Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
55
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 6

//from assemblyinfo.cs [assembly: ComVisible(true)]

// The GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid(“5252f41f-a404-43eb-8d55-8fbdeb2011df”)]

[assembly: AssemblyVersion(“1.0.0.0”)] [assembly: AssemblyFileVersion(“1.0.0.0”)]

[assembly: AllowPartiallyTrustedCallers()]

At this point, you can integrate the Helper class into the custom HTTP handler. Rather than passing the role information for the user in the clear as a simple string, the custom handler will instead calculate the signed hash for all of the roles.

ublic override string OverrideExecuteUrlPath()

{

//gets called just before control is handed back to IIS6 HTTPContext c = this.Context;

StringBuilder userRoles = new StringBuilder();

RolePrincipal rp = (RolePrincipal)c.User;

string rolesHeader;

if ( (rp != null) && (rp.GetRoles().Length > 0) )

{

foreach (string role in rp.GetRoles()) userRoles.Append(role + “;”);

rolesHeader = userRoles.ToString(0, userRoles.Length - 1); rolesHeader = rolesHeader + “,” +

Helper.HashStringValue(rolesHeader);

}

else

rolesHeader = String.Empty;

this.ExecuteUrlHeaders.Add(“Roles”, rolesHeader); return null;

}

The extra code appends the HMACSHA1 hash of the role string to the end of the custom header. Now when you log in to the ASP application, the header looks like:

HTTP_ROLES = Administrators;Regular User;Valued

Customer,5F9AFD42A9ABCE50FE651A39A1F5EB63E5142D21

To use the hash helper from inside of the ASP.NET application, you also need to add an assembly reference because the helper is deployed in the GAC:

<compilation debug=”true”> <assemblies>

<add assembly=”HashLibrary, Version=1.0.0.0, Culture=neutral,

PublicKeyToken=729492b6d2638318” /> </assemblies>

</compilation>

282

Integrating ASP.NET Security with Classic ASP

The only work left to do at this point is make the hash helper available to the classic ASP application. Because the helper assembly was already compiled with the necessary attributes to make it visible in COM, you just need to register the assembly with the regasm.exe utility:

%windir%\Microsoft.NET\Framework\v2.0.50727\regasm HashLibrary.dll

The result of running regasm is that the Helper class is registered as a COM type in the Windows Registry and is associated with the type library GUID that was defined in the helper project’s AssemblyInfo.cs file. Because the intent for now is to just call the Helper class from ASP, there wasn’t any additional information specified in the Helper project to give the Helper class a fixed COM CLSID. Classic ASP uses late-bound COM calls anyway so the extra work to configure the Helper class with a fixed class ID isn’t necessary.

You can use the hash helper from ASP as shown here:

<%

Dim objHelper, signedRoles, strRoles, strRolesHash, arrRoles

if (Request.ServerVariables(“HTTP_ROLES”) <> “”) then

signedRoles = split(Request.ServerVariables(“HTTP_ROLES”),”,”)

strRoles = signedRoles(0) strRolesHash = signedRoles(1)

Set objHelper = Server.CreateObject(“HashLibrary.Helper”) result = objHelper.ValidateHashCOM(strRoles, strRolesHash) if (result = true) then

arrRoles = split(strRoles,”;”) For Each role In arrRoles

Response.Write(role) + “<br/>” Next

else

Response.Write(“No valid roles were found for the user.”) end if

else

Response.Write(“No roles were found for the user.”) end if

%>

Assuming that a custom “Roles” header was sent, this ASP code splits the value into two parts: the string containing the actual role information and the string containing the digital signature of the role string. With these two values, the ASP code creates an instance of the Helper class using COM, and then calls the ValidateHashCOM method to verify the digital signature that was sent in the header. Because the custom HTTP handler is using the same key material, the Helper class successfully validates that the signature in the custom header is valid.

You can try testing the negative case by tweaking the custom handler to include bogus data in the signature:

this.ExecuteUrlHeaders.Add(“Roles”, rolesHeader + “1”);

Because the digital signature is the last part of the custom HTTP header, appending an extra character creates an invalid hash value. Now when you try to run the sample ASP code, the hash verification will fail.

283

Chapter 6

You have seen how the hash verification is handled, with the signature being created in the handler and then validated in classic ASP. You can integrate this kind of logic into whatever ASP code you currently use for authorization. The logic for splitting the custom header and verifying it can easily be wrapped in a custom include file or function without necessarily affecting any other code in your ASP application that depends on retrieving and checking role information.

Full Code Listing of the Hash Helper

Since the hash Helper class was shown piecemeal earlier, the Helper class is shown in its entirety here:

using System;

using System.Collections.Generic; using System.Text;

using System.Security.Cryptography;

namespace HashLibrary

{

public class Helper

{

private static string hashKey = “179C4AB2765118F23CCB273EF2BB31016154F01033F237F1BC0B04662232D51BE7416119B88D52B5C3 46CA9E03A4EA34875C4D15A976A35315553246494781D5”;

private static byte[] bKey;

static Helper()

{

//Cache the byte representation of the signing key bKey = ConvertStringKeyToByteArray(hashKey);

}

public static byte[] ConvertStringKeyToByteArray(string stringizedKeyValue)

{

byte[] keyBuffer = new byte[64];

if (stringizedKeyValue.Length > 128) throw new ArgumentException(

“This method is hardcoded to accept only a 128 character string”);

for (int i = 0; i < stringizedKeyValue.Length; i = i + 2)

{

//Convert the string key - every 2 characters represents 1 byte keyBuffer[i / 2] =

Byte.Parse( stringizedKeyValue.Substring(i, 2),

System.Globalization.NumberStyles.HexNumber

);

}

return keyBuffer;

}

public static string ConvertByteArrayToString(byte[] value)

284

Integrating ASP.NET Security with Classic ASP

{

StringBuilder sb = new StringBuilder(128);

if (value.Length > 64)

throw new ArgumentException(

“This method is hardcoded to accept only a byte[64].”);

foreach (byte b in value)

{

sb.Append(b.ToString(“X2”));

}

return sb.ToString();

}

public static string HashStringValue(string valueToHash)

{

using (HMACSHA1 hms = new HMACSHA1(bKey))

{

return ConvertByteArrayToString( hms.ComputeHash(Encoding.Unicode.GetBytes(valueToHash)));

}

}

public static bool ValidateHash(string value, string hash)

{

using (HMACSHA1 hms = new HMACSHA1(bKey))

{

if (HashStringValue(value) != hash) return false;

else

return true;

}

}

#region COM support public Helper() { }

public bool ValidateHashCOM(string value, string hash)

{

return Helper.ValidateHash(value, hash);

}

#endregion

}

}

Summar y

Prior to ASP.NET 2.0 and IIS6, your options for integrating authentication and authorization rules between ASP.NET and classic ASP were limited. You could write awkward redirection-based logic that moved data around on query-strings, or you could invest a fair amount of effort attempting to wrap ASP.NET functionality inside of a Web Service.

285

Chapter 6

With IIS6 and ASP.NET 2.0, extra logic was added to the runtimes of both products that finally makes it easier to integrate the ASP and ASP.NET environments. IIS6 added a new feature called wildcard mappings that allows arbitrary ISAPI extensions to participate in the request lifecycle of any resource. This allows you to route all .asp requests to ASP.NET. ASP.NET 2.0 includes the necessary logic to recognize when wildcard mappings are being used. Unlike earlier versions of ASP.NET, ASP.NET 2.0 will route a request to IIS6 for further processing.

The combination of IIS6 wildcard mappings and ASP.NET 2.0’s DefaultHandler means that you can now use ASP.NET authentication and authorization in conjunction with a classic ASP site. The basic steps necessary to enable this integration are:

1.Use wildcard mappings to route all .asp requests to the ASP.NET ISAPI extension.

2.Add some .aspx pages to your classic ASP application. The basic ASP.NET page that you will need is some kind of login page.

3.Although the ASP and ASP.NET pages all live in the same directory structure, you can still add a web.config file into this structure for the ASP.NET pages. This web.config file includes settings to turn on forms authentication, define URL authorization rules, and enable the Membership and Role Manager features for automatic authentication and authorization support.

4.Optionally, you can author a custom HTTP handler that derives from DefaultHandler. This is only necessary if you plan to pass information from ASP.NET over to classic ASP. For example, as was demonstrated in this chapter, a custom handler can pass the role information from Role Manager over to ASP using a custom HTTP header

After steps 1–3 have been accomplished (and optionally step 4), access to your ASP pages is controlled by the authentication and authorization mechanisms of ASP.NET. This allows you to migrate the authentication and authorization rules for your mixed application environments exclusively into ASP.NET.

286

Session State

Session state probably doesn’t strike most people as having much of anything to do with security. However, some security-related design points are worth touching on when thinking about how session state is used in an application. In ASP.NET 2.0 some new functionality was added around securing cookieless sessions as well as locking down behavior in lower trust levels.

This chapter covers the following topics on ASP.NET 2.0 session state:

Session state and the concept of a logon session

How session data is partitioned across applications

Cookie-based session IDs

Cookieless sessions and Session ID regeneration

Protecting against session state denial-of-service attacks

Trust level restrictions when using session state

Database security when using storing session state in SQL Server

Securing the out of process state server

Does Session State Equal Logon Session?

An architectural question that comes up time and time again with session state is whether session state can be considered equivalent to a logon session. Hopefully after reading this section, you will agree that the answer to this question is unequivocally no! When developers ask about having the concept of a logon session object in ASP.NET, not only are they looking for a convenient storage location associated with a user, but they are also usually looking for a mechanism that prevents problems such as duplicate logins. (A workaround using forms authentication for this was shown earlier in Chapter 5.)

Chapter 7

However, in ASP.NET session state is a service that is always available on each and every page in an application. There is no concept of having to authenticate to obtain a valid session object. More importantly, no mechanism inside of ASP.NET enforces validity of a session identifier (that is, is the identifier a value that was originally generated by ASP.NET?). As long a browser is able to send a well-formed session identifier to ASP.NET, and the session identifier meets some basic syntax checks, the corresponding session data is available to the application.

Contrast this with something like forms authentication where, in the default configuration, it is next to impossible to create a forged forms authentication ticket. (You would need to guess an encryption key as well as the key used for the HMACSHA1 signature.) The problem with depending on session state as an indicator of a logon session is that unlike forms authentication, it is trivial to create a valid session identifier.

Because a session identifier is nothing more than a 120-bit random number encoded using letters and numbers (this works out to a 24-character cookie value due to the way session state encodes the random number), you or I can easily create a perfectly valid session identifier. Of course, if you send such an identifier to ASP.NET, there probably isn’t going to be any session data associated with it. (You have 2^120 possible combinations to guess if you were actually trying to grab someone else’s session.) Instead ASP.NET spins up a new session object for you based on the ID.

If your application’s code stored data inside of the Session object that indicated logon information, potentially even information indicating the logon status, you can quickly see how with a trivial client-side “attack,” a user already logged on can quickly get into a logged-off state. There is another more subtle problem with using session state as a kind of logon session service: session identifiers cannot flow across domains.

The configuration options for session state, unlike forms authentication, don’t include options for setting a cookie domain or a cookie path. Furthermore, when using the cookieless mode of operation, there is no facility equivalent to the cross-application redirection capability in forms authentication. For both of these reasons, attempting to keep track of a logon session across a set of applications running under different DNS addresses (although at least sharing a common domain suffix for example, mycompany

.com) is simply not possible with cookieless session state. The cookieless identifier that associates a user to session information will be different across various applications and no functionality is available to synchronize session state data from multiple applications.

A second flaw with attempting to use session state as a surrogate logon session service is that even if multiple applications share the same DNS namespace (meaning that all the applications run as virtual directories underneath www.mycompany.com), the very nature of session state is to segment data by application. You take a closer look at this in the next section, but in a nutshell the session state from application A is never available to session state in application B. It doesn’t matter whether you use out- of-process (OOP) session state in an attempt to make session data available across a web farm; even the OOP modes of operation segment data from different applications.

A final shortcoming of using session state for tracking logon status is the inability to set the Secure property of the session state cookie (assuming that you are using cookied mode of course). Unlike forms authentication, the session state cookie always flows across the network regardless of the state of any SSL security on the connection. If you think about it, this makes sense for a feature like session state because many applications would break if the data in session randomly became unavailable when a user surfed between secure and insecure pages.

288

Session State

This means that session state as implemented in the default providers that ship with ASP.NET 2.0 is not explicitly associated to a user. Although ASP.NET 2.0 exposes new extensibility hooks that allow you or a third party to write such functionality, out-of-the-box session state is basically an anonymous data storage mechanism. As long you have a valid identifier, you can get and set session data. However, this is exactly the functionality you want to avoid with a logon session; the whole point of a logon session is that it requires authentication to obtain a session, and once established there is a persistent association between an authenticated user and the actual session data.

About the only situation where session state could be used is in a single-application scenario. If you are writing a single application and you never need to flow authentication information to any other application, you could potentially turn session state into a surrogate logon session service. Technically, you could create a login form, and when a user sent valid credentials, instead of issuing a forms authentication ticket, you could write some information into session state. When the user returned to the site, and the session state was still active, you could check the session data to determine the logged on status.

Even for this limited scenario, there is another argument against using session state as an indication of the logged-in status for a user. Session state can potentially live forever; there is no concept of an absolute expiry for session state data. Instead, as long as a request is periodically made with the session state expiration time window, the time to live of the session data will be renewed. Unlike forms authentication, there is no way to lock down the lifetime of session data with an absolute expiration. For secure sites, the last thing you want is for an authenticated user to “live forever” on the website.

The following table compares the important security features of forms authentication against session state and shows why session state should be used solely as a convenient data storage service, not as a login mechanism.

Security Feature

Forms Authentication

SessionState

 

 

 

Control DNS domain of cookie

Yes

No

Control path of cookie

Yes

No

Require SSL for cookie

Yes

No

Information is shareable across applications

Yes

No

Supports absolute expirations

Yes

No

A valid Identifier can be easily forged

No

Yes

 

 

 

Of course, from this discussion you might be wondering if you should use session state at all! The best way to think about session data is to treat session state as if it were data stored in forms variables on a page. The one major difference being that you don’t need to move data back and forth in an HTML form when you use session state. Instead, session state acts as a server-side store for this type of information. From the point of view of data security, you should treat session state data as if it were being sent back and forth in a web page.

For example, if you were filling out an online insurance application, you might choose to store each page’s entries in session state to make the application process run faster. From a security and privacy standpoint though, this data could just as easily have ended up in hidden fields or in form elements located on different web pages. As a result, you would want to ensure that any session state data entered

289

Chapter 7

during the application process came from pages that were submitted over an SSL connection. Similarly, you would want to process or display this information to the user only over an SSL connection. From a developer standpoint, you would need to be diligent enough to ensure that this type of information was not accessed from an insecure page such as a non-SSL home page.

Session Data Par titioning

Another question that frequently arises is around data partitioning of session data between applications. From time to time, someone will have a panic attack because, at first glance, session state looks as if it would leak data from one application into another. Especially in the case of out-of-process session state, where all servers and all applications share a central database (or session server), it is understandable why some developers are a bit leery about accidental data sharing.

The example here starts with the simpler case of in-process session state. When using the in-process mode of operation (which in ASP.NET 2.0 is now really an in-process session state provider, because Session state is now a provider-based feature as well), the data storage mechanism that is used is the ASP.NET Cache object. Because the Cache object manages a chunk of memory inside an application domain, you automatically gain the benefit of partitioning. There is no remoting capability built into either the Cache object or the in-process session state provider.

As a result, short of attaching a debugger or using Win32 APIs to poke around in memory, there isn’t anyway that application A’s session state can accidentally show up inside of application B. Each ASP.NET application on the web server lives in its own application domain, and there is no mechanism to reach out and access session data across application domains. Of course, nothing prevents you from writing some cross-appdomain remoting objects that would give you this capability, but realistically if you want to go down that road, you would probably want to write a custom ASP.NET session state provider that runs against a central application domain used for storing common session state data.

Now for the potentially more worrisome scenarios: What happens when you run with one of the out-of- process session state providers? Is there some way that application A could reach into application B’s session state data when using the SQL Server-based provider? Clearly this isn’t the case, because if that were actually happening ASP.NET’s out-of-process session state would have been broken all the way back in ASP.NET 1.0.

In the case of both the OOP session server, and the OOP provider that uses SQL Server, ASP.NET includes an application identifier with the session state data. For example, if you take two sample applications using the same session state configuration:

<sessionState mode=”SQLServer” sqlConnectionString=”server=.;Integrated Security=true” />

and both applications manipulate session data with the following code (the application name is different in the other application of course):

Session[“somevariable”] = “Application A: somedata” + DateTime.Now.ToString();

you end up with two different sets of data in the session state SQL database. In the case of the SQL database, two tables are used: ASPStateTempApplications and ASPStateTempSessions. The temporary applications table shows information for the two different ASP.NET applications:

290

Session State

AppId AppName

----------- ------------------------------------------

145274326 /lm/w3svc/1/root/chapter7/sessionstateappa

145274325 /lm/w3svc/1/root/chapter7/sessionstateappb

ASP.NET uses the Internet Information Services (IIS) metabase path of each application as an identifier when partitioning session state data. Looking in the table that stores the actual session state data, along with a number of other columns containing data and lock status, there is a SessionID column:

SessionId

--------------------------------

c5eyzd2vqefu3bnvyk03zh5508a8b5d5 c5eyzd2vqefu3bnvyk03zh5508a8b5d6

At first glance, the IDs from the two applications look almost exactly the same. Take a look at the bolded portion of the session identifier though. This portion of the identifier differs between the two rows of data because the extra eight characters (padded so there are two hex characters per byte of application ID) are actually the application identifiers from the ASPStateTempSessions table. The first 24 characters in the SessionId column are the same because these 24 characters represent that actual session identifier that is sent back to the browser in the cookie. You will also see this value if you retrieve the

Session.SessionID property.

So, things become quite a bit clearer around data partitioning for the OOP modes of operation. ASP.NET keeps track of the different applications that have been registered in the OOP session state stores. Whenever a request comes through to get or set data, the primary key (or the cache lookup key in the case of the session state server) for the data includes the client’s session identifier and some extra information identifying the specific web application that originated the request.

One interesting point is obvious from looking at how the applications are stored in the database. For applications that are deployed on a web farm, you must ensure that each application installation is made to the same virtual web server on each web server. If you accidentally mix up the virtual web servers during installation, one of two things will happen:

One of your application installations will end up with a totally different metabase path, and it will store session data separately from all of the other application installs.

If you have applications spread out across your web servers, the potential exists that you accidentally install application A in application B’s virtual webserver, and vice versa. If that happens, you probably will end up with exceptions inside of your web applications when you attempt to cast session data retrieved from the wrong row of session data back to an incompatible data type.

Cookie-Based Sessions

Storing the session identifier in a cookie is the most common mode of operation for developers — it is also the default mode of operation for ASP.NET 2.0. Because it follows the programming model as session state in Classic ASP, many developers never need to deal with the cookieless mode of operation.

You saw earlier that session state providers ensure that data in the back-end data store is properly partitioned by application. This is important because if you look at the session identifier in use across multiple applications on the same web server, you see that it is the exact same identifier. The application ID based partitioning is hidden inside of the session state providers.

291