How To: Instrument ASP.NET Applications for Security
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. |
patterns & practices Developer Center
J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley, Kishore Gopalan
Microsoft Corporation
August 2005
- ASP.NET version 2.0
This How To shows you how to use custom health monitoring events to instrument your ASP.NET application to track security-related events and operations. ASP.NET version 2.0 provides health monitoring that includes instrumentation for many standard security-related and infrastructure events common to all ASP.NET applications. If you want to track additional security-related activity not covered by the standard events, or if you want to report on application-specific events, you need to create and raise custom events. This How To includes three samples that show how to build custom events that you can use to track password change events, user account lockout, and access to sensitive business logic.
Objectives
Overview
Platform Default Security Events
Additional Custom Security Events
Sample: Password Change Event
Sample: Membership Account Locked Event
Sample: Access Sensitive Method Event
- Identify the security related events that are raised automatically.
- Learn about additional security related events you might want to track.
- Raise custom events to track specific security related activity.
- Instrument your code to detect password changes.
- Instrument your code to detect when the membership system locks out an account.
- Instrument your code to track access to sensitive business logic.
To react promptly to security incidents or infrastructure and application failures, you need to instrument and monitor your ASP.NET applications. ASP.NET version 2.0 runtime components and controls are instrumented for health monitoring and they raise events for many common situations. This includes login failures and successes when using the ASP.NET membership system, attempts to tamper with or reuse forms authentication tickets, and infrastructure issues such as disk access failures. By default, the ASP.NET health monitoring system is configured to report all security-related error events, and all infrastructure-related error events, to the Windows event log.
When planning the instrumentation of an ASP.NET application, you should start by deciding the standard ASP.NET events that you want to report, where you want to report them, and how you want to monitor them. ASP.NET version 2.0 includes event logging providers to write to the following locations:
- Windows event log
- SQL Server database
- E-mail recipient
- Windows Management Instrumentation (WMI)
You can also create your own custom event providers. You can use standard monitoring tools and products such as Microsoft Operations Manager (MOM) to monitor events.
The class hierarchy of the standard events is shown in Figure 1, together with the major event code associated with each event. Some events also log a detail code as well as a major code, but these are not shown in Figure 1. For more information about the standard event codes, see WebEventCodes Class of the .NET Framework Class Library on MSDN.
Figure 1. Class hierarchy of the standard event classes with major event codes
By default, ASP.NET health monitoring is configured to log to the Windows event log all events of type WebFailureAuditEvent (including its descendants, WebAuthenticationFailureAuditEvent and WebViewStateFailureAuditEvent) and all events of type WebBaseErrorEvent (and its descendants, WebErrorEvent and WebRequestErrorEvent). These classes are shown highlighted in Figure 1.
You can configure health monitoring to log a different set of events. You can select specific event codes by specifying a range of codes to log, and you can log to different event sinks through different event providers. For more information about how to configure ASP.NET health monitoring, see How To: Use Health Monitoring in ASP.NET 2.0.
ASP.NET version 2.0 supports many events that you can use to monitor the health of your application. The most critical of these are those of type WebErrorEvent and WebFailureAuditEvent (including its descendants, WebAuthenticationFailureAuditEvent and WebViewStateFailureAuditEvent), which are logged by default, while others may be added to aid investigation in the case of an attack.
The following events are logged to the Windows event log by default.
The following major event codes are logged for events of type WebAuthenticationFailureAuditEvent.
- AuditFormsAuthenticationFailure (code 4005). This is used to identify brute force or dictionary attacks when using forms authentication classes. This code may also be accompanied by one of the following detail codes:
- ExpiredTicketFailure. This is used to identify cookie replay attacks.
- InvalidTicketFailure. This is used to identify authentication cookie tampering.
- AuditMembershipAuthenticationFailure (code 4006). This is used to identify brute force or dictionary attacks when using the membership feature.
The following major event code is logged for exceptions of type WebViewStateFailureAuditEvent:
- AuditInvalidViewStateFailure (code 4009). This is used to identify tampering with ViewState.
The following major event codes are logged for events of type WebFailureAuditEvent, which are associated with authorization failures:
- AuditFileAuthorizationFailure (code 4008). This is used to identify attempts to access unauthorized files or folders.
- AuditUnhandledAccessException (code 4011). This is used to identify attempts of unauthorized access to resource.
- AuditUnhandledSecurityException (code 4010). This is used to identify attempts to perform actions not allowed by the current trust level.
- AuditUrlAuthorizationFailure (code 4007). This is used to identify attempts to access an unauthorized path or page.
The following major event codes are logged for exceptions of type WebErrorEvent, which are associated with compilation or configuration errors and may indicate unauthorized alteration of the content of your Web site:
- WebErrorCompilationError (code 3007). This indicates that an error occurred during application compilation.
- WebErrorConfigurationError (code 3008). This indicates that a configuration error occurred.
- WebErrorObjectStateFormatterDeserializationError (code 3011). This indicates that an error deserializing internal state objects occurred.
- WebErrorOtherError (code 3009). This indicates that an unclassified error occurred.
- WebErrorParserError (code 3006). This indicates that a parser error occurred.
- WebErrorPropertyDeserializationError (code 3010). This indicates that an error deserializing internal state objects occurred.
The following major event codes are logged for exceptions of type WebRequestErrorEvent, which are associated with runtime errors, and may indicate an attack on your Web site:
- DiskOutputCacheInformation (code 5003). This indicates that a disk output cache event occurred. This event is always accompanied by a detail code giving more information.
- DiskOutputCacheQuotaExceeded (code 5001). This indicates that the disk output cache quota was exceeded.
- RuntimeErrorPostTooLarge (code 3004). This indicates that the size of the posted information exceeded the allowed limits.
- RuntimeErrorRequestAbort (code 3001). This indicates that the Web request has been aborted.
- RuntimeErrorUnhandledException (code 3005). This indicates that an unhandled exception occurred.
- RuntimeErrorValidationFailure (code 3003). This indicates that a validation error occurred.
- RuntimeErrorViewStateFailure (code 3002). This indicates that a view state failure occurred.
The following events are not logged by default.
The following events can be logged for detecting application availability. These are not on by default, but we recommend that all of them are turned on in order to detect potential attacks:
- ApplicationStart (code 1001) / ApplicationShutdown (code 1002). These events indicate application startup and shutdown, and they are always accompanied by a detail code giving more information. If you see a large quantity of these events, your application may be the target of a denial of service attack.
- ApplicationCompilationStart (code 1003) / ApplicationCompilationEnd (code 1004). These events indicate the compilation of the application has started and finished. If you see these events, it may be an indication of unauthorized modification of your application content.
For additional forms authentication information, log the following events:
- AuditFormsAuthenticationSuccess (code 4001). This is used to maintain audit trail of successful forms authentication, which can then be retraced if the system is compromised, when using forms authentication classes.
- AuditMembershipAuthenticationSuccess (code 4002). This is used to maintain audit trail of successful authentications in the ASP.NET membership system, which can then be retraced if the system is compromised, when using the membership feature.
To provide additional authorization information, log the following events:
- AuditFileAuthorizationSuccess (code 4004). This is used to maintain an audit trail, which can then be retraced to identify all successful file accesses by an attacker if the system is compromised.
- AuditUrlAuthorizationSuccess (code 4003). This is used to maintain an audit trail, which can then be retraced to identify all successful URL and path accesses by an attacker if the system is compromised.
The WebHeartbeatEvent may be logged to provide an audit trail that monitors normal operation of your application:
- ApplicationHeartbeat (code 1005). This is used to log a heartbeat for the application. The heartbeat interval is defined by the heartBeatInterval attribute of the <healthMonitoring> element. The default value is 0, indicating no heartbeat.
These events may be logged to provide an audit trail of normal request completion and abort:
- RequestTransactionComplete (code 2001). This indicates the Web request was completed.
- RequestTransactionAbort (code 2002). This indicates the Web request was aborted.
There are additional custom security events that you can instrument to improve your ability to detect and understand attacks on your application. The following list identifies some additional security events that you should consider logging. In addition to the events that follow, you might also have to instrument unique events to monitor critical functionality in your application.
Following is a list of authorization scenarios for which you can create custom events in order to improve visibility on the health of your application:
- Accessing in memory object/keys. This is used to instrument success and failure events, can be used to identify objects/keys compromise.
- Roles Authorization success events. This is used to maintain audit trail for user activities.
- Roles Authorization failure events. This is used to identify attempts at unauthorized access.
Instrumenting the following events can aid your ability to track user activities and detect anomalies in user behavior:
- Session lifetime events
- Session creation
- Session termination
- Session timeout
- Session expiration
Instrumenting the following events can aid your ability to track anomalies in user account changes:
- Password resets/changes
- Account creation/deletion/modification/lockout
- Roles assignment
This sample provides a custom Web event that you can use to record when a user changes his or her password in an application that uses ASP.NET membership to authenticate users with forms authentication.
To create a password changed Web event
To build a Web event, create a class in a class library that derives from System.Web.Management.WebSuccessAuditEvent. Use the following code.
using System; using System.Web.Management; using System.Text; using System.Web; namespace MyWebEvents { public class PasswordChangedEvent : WebSuccessAuditEvent { public PasswordChangedEvent(string msg, object eventSource, int eventCode) : base(msg, eventSource, eventCode) { } public PasswordChangedEvent(string msg, object eventSource, int eventCode, int eventDetailCode) : base(msg, eventSource, eventCode, eventDetailCode) { } public override void FormatCustomEventDetails( WebEventFormatter formatter) { base.FormatCustomEventDetails(formatter); // Add custom data. formatter.AppendLine(""); formatter.IndentationLevel += 1; formatter.AppendLine( "******** SampleWebSuccessAuditEvent Start ********"); formatter.AppendLine(string.Format("Request path: {0}", RequestInformation.RequestPath)); formatter.AppendLine(string.Format("Request Url: {0}", RequestInformation.RequestUrl)); // Display custom event message. formatter.AppendLine("Password changed"); formatter.AppendLine( "******** SampleWebSuccessAuditEvent End ********"); formatter.IndentationLevel -= 1; } } }
Compile the class library to build an assembly.
To instrument an application with the password changed event
Create a new ASP.NET Web application that includes the following Login.aspx page. Note that this page contains CreateUserWizard and ChangePassword controls. Within the OnChangePassword event handler, the custom PasswordChangedEvent is raised.
<%@ Page Language="C#" %> <%@ Import namespace="System.Web.Management" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script > protected void OnChangePassword(object sender, EventArgs e) { MyWebEvents.PasswordChangedEvent pcEvt = new MyWebEvents.PasswordChangedEvent( "Password changed", this, WebEventCodes.WebExtendedBase + 1); pcEvt.Raise(); } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head > <title>Untitled Page</title> </head> <body> <form id="form1" > <div> <asp:CreateUserWizard ID="CreateUserWizard1" > <WizardSteps> <asp:CreateUserWizardStep > </asp:CreateUserWizardStep> <asp:CompleteWizardStep > </asp:CompleteWizardStep> </WizardSteps> </asp:CreateUserWizard> </div> <asp:ChangePassword ID="ChangePassword1" OnChangedPassword="OnChangePassword"> </asp:ChangePassword> </form> </body> </html>
Note Default ASP.NET configuration for the membership system results in the creation and use of a membership database in a SQL Express database in your application's \app_data folder. For more information about how to use the ASP.NET membership system, see How To: Use Membership in ASP.NET 2.0.
Add a Web.config file to your application and add the following configuration, to configure your application for forms authentication.
<system.web> ... <authentication mode="Forms" /> <authorization> <deny users="?" /> </authorization> ... </system.web>
Configure health monitoring for the custom event by adding the following <healthMonitoring> element to your application's Web.config file within the <system.web> element.
<healthMonitoring> <eventMappings> <add name="Password Change Event" type="MyWebEvents.PasswordChangedEvent,MyWebEvents"/> </eventMappings> <rules> <add name="Custom event" eventName="Password Change Event" provider="EventLogProvider" minInterval="00:00:01"/> </rules> </healthMonitoring>
Run the application. Use the CreateUserWizard control to create a new user. The default membership configuration requires a password of at least 7 characters in length with at least one non-alphanumeric character.
Use the ChangePassword control to change the user's password.
Examine the Windows application event log to verify that the custom event has been raised and logged. You should see details similar to the following.
Event Type: Information Event Source: ASP.NET 2.0.50215.0 Event Category: Web Event Event ID: 1312 Date: 22/06/2005 Time: 12:09:50 User: N/A Computer: MachineName Description: Event code: 100002 Event message: Password changed Event time: 22/06/2005 12:09:48 Event time (UTC): 22/06/2005 11:09:48 Event ID: 1cc7c6fd96ea459892fd8896ceabf912 Event sequence: 5 Event occurrence: 1 Event detail code: 0 Application information: Application domain: 39a139ed-2-127639121611001296 Trust level: Full Application Virtual Path: /HealthMonitoring Application Path: C:\Documents and Settings\{UserName}\my documents\visual studio\Projects\Websites\HealthMonitoring\ Machine name: MachineName Process information: Process ID: 3052 Process name: WebDev.WebServer.EXE Account name: DomainName\UserName Request information: Request URL: https://localhost:2076/HealthMonitoring/default.aspx Request path: /HealthMonitoring/default.aspx User host address: 127.0.0.1 User: UserName Is authenticated: True Authentication Type: Forms Thread account name: DomainName\UserName Custom event details:
******* SampleWebSuccessAuditEvent Start ******* Request path: /HealthMonitoring/default.aspx Request Url: https://localhost:2076/HealthMonitoring/default.aspx Password changed ******* SampleWebSuccessAuditEvent End *******
For more information, see Help and Support Center at https://go.microsoft.com/fwlink/events.asp.
```
The following sample shows how to raise a custom Web event when an ASP.NET membership user account is locked because of too many attempts to login using an incorrect password.
To create an account locked Web event
To build a Web event, create a class in a class library that derives from System.Web.Management.WebAuthenticationFailureAuditEvent. Use the following code after adding a reference to System.Web
using System; using System.Web.Management; using System.Text; using System.Web; namespace MyWebEvents { public class AccountLockedEvent : WebAuthenticationFailureAuditEvent { public AccountLockedEvent(string msg, object eventSource, int eventCode, string nameToAuthenticate) : base(msg, eventSource, eventCode, nameToAuthenticate) { } public AccountLockedEvent(string username, string msg, object eventSource, int eventCode, int eventDetailCode, string nameToAuthenticate) : base(msg, eventSource, eventCode, eventDetailCode, nameToAuthenticate) { } } }
Compile the class library to build an assembly.
To instrument an application with the account locked event
Create a new ASP.NET Web application that includes the following Login.aspx page. Note that this page contains Login and CreateUserWizard controls. Within the Login1_LoginError event handler, the custom AccountLockedEvent is raised if the authentication error is as a result of the user account being locked.
Note that this example traps the LoginError event of the Login control to detect that a user could not be authenticated. If you are using the membership system but not using the login controls, you can retrieve the value of the MembershipUser.IsLockedOut property at any time to detect if a user account is locked.
<%@ Page Language="C#" %> <%@ Import namespace="System.Web.Management" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script > protected void Login1_LoginError(object sender, EventArgs e) { MembershipUser user = Membership.GetUser(Login1.UserName); // Check user exists; if so, was failure due to locked out? if (user != null) { if (user.IsLockedOut) { AccountLockedEvent testEvent = new AccountLockedEvent("Account Locked", this, WebEventCodes.WebExtendedBase + 2, Login1.UserName); testEvent.Raise(); } } } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head > <title>Untitled Page</title> </head> <body> <form id="form1" > <div> <asp:Login ID="Login1" OnLoginError="Login1_LoginError"> </asp:Login> </div> <asp:CreateUserWizard ID="CreateUserWizard1" > <WizardSteps> <asp:CreateUserWizardStep > </asp:CreateUserWizardStep> <asp:CompleteWizardStep > </asp:CompleteWizardStep> </WizardSteps> </asp:CreateUserWizard> </form> </body> </html>
Note Default ASP.NET configuration for the membership system results in the creation and use of a membership database in a SQL Express database in your application's \app_data folder. For more information about how to use the ASP.NET Membership system, see How To: Use Membership in ASP.NET 2.0.
Add a Web.config file to your application and add the following configuration to configure your application for forms authentication.
<system.web> ... <authentication mode="Forms" /> <authorization> <deny users="?" /> </authorization> ... </system.web>
Configure health monitoring for the custom event by adding the following <healthMonitoring> element to your application's Web.config file within the <system.web> element.
<healthMonitoring> <eventMappings> <add name="Account Locked Event" type="MyWebEvents.AccountLockedEvent,MyWebEvents"/> </eventMappings> <rules> <add name="Custom event" eventName="Account Locked Event" provider="EventLogProvider" minInterval="00:00:01"/> </rules> </healthMonitoring>
Run the application. Use the CreateUserWizard control to create a new user. The default membership configuration requires a password of at least 7 characters in length with at least one non-alphanumeric character.
Enter data into the Login control to perform user authentication. Use the user name you just created, but deliberately enter an incorrect password. Repeat this 5 times within a 10 minute window.
Note The number of invalid password attempts is determined by the maxInvalidPasswordAttempts attribute of the configured membership provider and the retry window by the passwordAttemptWindow attribute. The default configuration for the ASP.NET membership system is defined in %Windows%\Microsoft.NET\Framework\version\Config\machine.config.default, which defines this attributes as 5 attempts within a 10 minute window.
Examine the Windows application event log to verify that the custom event has been raised and logged. You should see details similar to the following. Note that the user name of the locked account is included near the bottom as Name to authenticate.
Event Type: Information Event Source: ASP.NET 2.0.50215.0 Event Category: Web Event Event ID: 1315 Date: 02/08/2005 Time: 11:28:17 User: N/A Computer: MachineName Description: Event code: 100001 Event message: Account Locked Event time: 02/08/2005 11:28:17 Event time (UTC): 02/08/2005 10:28:17 Event ID: aa11f46a63bf49ccb6e2c642735ef26e Event sequence: 5 Event occurrence: 1 Event detail code: 0 Application information: Application domain: 39a139ed-1-127674517374818688 Trust level: Full Application Virtual Path: /HealthMonitoring Application Path: C:\Documents and Settings\{UserName}\my documents\visual studio\Projects\Websites\HealthMonitoring\ Machine name: MachineName Process information: Process ID: 2972 Process name: WebDev.WebServer.EXE Account name: DomainName\UserName Request information: Request URL: https://localhost:2076/HealthMonitoring/login.aspx Request path: /HealthMonitoring/login.aspx User host address: 127.0.0.1 User: Is authenticated: False Authentication Type: Thread account name: DomainName\UserName Name to authenticate: UserName Custom event details: For more information, see Help and Support Center at https://go.microsoft.com/fwlink/events.asp.
The following sample shows how to raise a custom Web event to track calls to a method containing sensitive business logic.
To create a sensitive method Web event
To build a Web event, create a class in a class library that derives from System.Web.Management.WebSuccessAuditEvent. The following example writes out the name of the Windows account that is currently authenticated and the name of the function being accessed.
using System; using System.Web.Management; using System.Text; using System.Web; namespace MyWebEvents { public class SensitiveFunctionEvent : WebSuccessAuditEvent { private string userID; private string functionName; public SensitiveFunctionEvent(string msg, object eventSource, int eventCode, string functionname) : base(msg, eventSource, eventCode) { functionName = functionname; // Obtain the HTTP Context and store authentication details userID = HttpContext.Current.User.Identity.Name; } public SensitiveFunctionEvent(string msg, object eventSource, int eventCode, int eventDetailCode, string functionname) : base(msg, eventSource, eventCode, eventDetailCode) { functionName = functionname; // Obtain the HTTP Context and store authentication details userID = HttpContext.Current.User.Identity.Name; } public override void FormatCustomEventDetails( WebEventFormatter formatter) { base.FormatCustomEventDetails(formatter); formatter.AppendLine("Function Name: " + functionName); formatter.AppendLine("User ID: " + userID); } } }
Add a reference to the System.Web assembly.
Compile the class library to build an assembly.
To instrument an application with the event
Create a new ASP.NET Web application that includes the following default.aspx page.
<%@ Page Language="C#" %> <%@ Import namespace="System.Web.Management" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <script > protected void Page_Load(object sender, EventArgs e) { SomeFunctionContainingSensitiveLogic(); } private void SomeFunctionContainingSensitiveLogic() { SensitiveFunctionEvent testEvent = new SensitiveFunctionEvent( "Sensitive function has been accessed", this, WebEventCodes.WebExtendedBase + 3, "SomeFunctionContainingSensitiveLogic"); testEvent.Raise(); // Some sensitive logic would appear below... // ... } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head > <title>Untitled Page</title> </head> <body> <form id="form1" > </form> </body> </html>
Add a Web.config file to your application and add the following configuration, to configure your application for Windows authentication.
<system.web> ... <authentication mode="Windows" /> ... </system.web>
Configure health monitoring for the custom event by adding the following <healthMonitoring> element to your application's Web.config file within the <system.web> element.
<healthMonitoring> <eventMappings> <add name="Sensitive Function Audit" type="MyWebEvents.SensitiveFunctionEvent,MyWebEvents"/> </eventMappings> <rules> <add name="Custom event" eventName="Sensitive Function Audit" provider="EventLogProvider" minInterval="00:00:01"/> </rules> </healthMonitoring>
Run the application.
Examine the Windows application event log to verify that the custom event has been raised and logged. You should see details similar to the following.
Event Type: Information Event Source: ASP.NET 2.0.50215.0 Event Category: Web Event Event ID: 1312 Date: 02/08/2005 Time: 12:23:50 User: N/A Computer: MachineName Description: Event code: 100003 Event message: Sensitive function has been accessed Event time: 02/08/2005 12:23:50 Event time (UTC): 02/08/2005 11:23:50 Event ID: 57d3761489b741978cae24b673f6b2a4 Event sequence: 4 Event occurrence: 1 Event detail code: 0 Application information: Application domain: 39a139ed-4-127674554288097376 Trust level: Full Application Virtual Path: /HealthMonitoring Application Path: C:\Documents and Settings\{UserName}\my documents\visual studio\Projects\Websites\HealthMonitoring\ Machine name: MachineName Process information: Process ID: 2972 Process name: WebDev.WebServer.EXE Account name: DomainName\UserName Request information: Request URL: https://localhost:2076/HealthMonitoring/default.aspx Request path: /HealthMonitoring/default.aspx User host address: 127.0.0.1 User: DomainName\UserName Is authenticated: True Authentication Type: NTLM Thread account name: DomainName\UserName Custom event details: Function Name: SomeFunctionContainingSensitiveLogic User ID: DomainName\UserName For more information, see Help and Support Center at https://go.microsoft.com/fwlink/events.asp.
Provide feedback by using either a Wiki or e-mail:
- Wiki. Security Guidance Feedback page: https://channel9.msdn.com/wiki/securityguidancefeedback/
- E-mail. Send e-mail to mailto:secguide@microsoft.com.
We are particularly interested in feedback regarding the following:
- Technical issues specific to recommendations
- Usefulness and usability issues
Technical support for the Microsoft products and technologies referenced in this guidance is provided by Microsoft Support Services. For product support information, please visit the Microsoft Product Support Web site.
Community support is provided in the forums and newsgroups:
To get the most benefit, find the newsgroup that corresponds to your technology or problem. For example, if you have a problem with ASP.NET security features, you would use the ASP.NET Security forum.
- External Contributors and Reviewers: Eric Marvets, Dunn Training and Consulting; Jason Taylor, Security Innovation; Rudolph Araujo, Foundstone Professional Services
- Microsoft Services and PSS Contributors and Reviewers: Tom Christian, Wade Mascia, Adam Semel
- Microsoft Product Group Contributors and Reviewers: Stefan Schackow
- Test team: Larry Brader, Microsoft Corporation; Nadupalli Venkata Surya Sateesh, Sivanthapatham Shanmugasundaram, Infosys Technologies Ltd.
- Edit team: Nelly Delgado, Microsoft Corporation; Tina Burden McGrayne, TinaTech Inc.
- Release Management: Sanjeev Garg, Microsoft Corporation
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. |