|
|
These blog entries are written by industry experts and leaders. We consider this content to be a good read for any software developer or web technologist.
Browse by Tags
All Tags » ASP.NET » Security (RSS)
-
SSL enables browsers to communicate with a web-server over a secure channel that prevents eavesdropping, tampering and message forgery. You should always use SSL for login pages where users are entering usernames/passwords, as well as for all other sensitive pages on sites (for example: account pages that show financial or personal information). Configuring SSL on Windows with previous versions of IIS has been a pain. Figuring out how to install and manage a certificate, and then associate it with a web-site, is something I bet most web developers don't know how to enable. The good news is that IIS 7.0 makes it radically easier to configure and enable SSL. IIS 7.0 also now has built-in support for creating "Self Signed Certificates" that enable you to easily create test/personal certificates that you can use to quickly SSL enable a site for development or test purposes. Using IIS 7.0 you can SSL enable an existing web site in under 30 seconds. The below tutorial demonstrates how to-do this. Step 1: Create a New Web Site We'll start by creating a new web-site using the new IIS 7.0 admin tool. This admin tool is a complete re-write of the previous IIS admin tool (and was written entirely in managed code using Windows Forms), and provides a more logical organization of web features. It provides a GUI admin experience for all ASP.NET and IIS settings: To create a new site on the box, right click on the "Web Sites" node in the left hand tree-view pane and choose the "Add Web Site" context menu option. Enter the appropriate details to create a new web-site: One nice feature of IIS7 on Windows Vista is that you can now have an unlimited number of sites on a box (previous versions of IIS on Windows Client only allowed 1 site). The 10 simultaneous request limitation on Windows Client versions of IIS also no longer exists with IIS 7.0. Once we've completed the above steps, we will now have a brand new site running on our IIS web-server. Step 2: Create a new Self Signed Certificate Before binding SSL rules to our new site, we need to first import and setup a security certificate to use with the SSL binding. Certificates are managed in IIS 7.0 by clicking the root machine node in the left-hand tree-view explorer, and then selecting the "Server Certificates" icon in the feature pane on the right: This will then list all certificates registered on the machine, and allow you to optionally import and/or create new ones. I could optionally go to a certificate authority like Verisign and purchase a certificate to import using this admin UI. Alternatively, I can create a "self-signed certificate" which is a test certificate that I can use during the development and testing of my site. To-do this, click the "Create Self-Signed Certificate" link on the right-hand side of the admin tool: Enter a name to use for the certificate (for example: "test") and hit ok. IIS7 will then automatically create a new self-signed crypto certificate for you and register it on the machine: Step 3: Enable HTTPS Bindings for our New Site To SSL enable the web-site we created earlier, select the web-site node in the left-hand tree-view, and the click the "Bindings" link in its "actions" pane on the right-hand side of the screen: This will then bring up a dialog that lists all of the binding rules that direct traffic to this site (meaning the host-header/IP address/port combinations for the site): To enable SSL for the site, we'll want to click the "Add" button. This will bring up an "add binding" dialog that we can use to add HTTPS protocol support. We can select the self-signed certificate we created earlier from the SSL certificate dropdownlist in the dialog, and in doing so indicate that we want to use that certificate when encrypting content over SSL: Click ok, and we now have SSL enabled for our site: Step 4: Test out the Site Add a "default.aspx" page to the site, and then try and hit it with the browser by typing https://localhost/default.aspx (note the usage of "https" instead of "http" to indicate that you want to connect over SSL). If you are using IE7, you'll likely see this anti-phishing error message kick in Don't panic if this happens - it is just IE being helpful by suggesting that a self-signed certificate on your local machine looks suspicious. Click the "Continue to this website" link to bypass this security warning and proceed to the site. You'll find that your default.aspx page is now running protected via SSL: You are all done. :-) Appendix: A Few Last SSL Notes A few last SSL related notes: - The IIS 7.0 admin tool has an "SSL Settings" node that you can select for each site, directory or file that allows you to control whether that particular resource (and by default its children) requires an SSL request in order to execute. This is useful for pages like a login.aspx page, where you want to guarantee that users can only enter their credentials when they are posting via an encrypted channel. If you configure the login.aspx page to require SSL, IIS 7.0 will block browsers from accessing it unless they are doing so over SSL.
- Within an ASP.NET page or handler, you can programmatically check whether the current request is using SSL by checking the Request.IsSecure property (it will return "true" if the incoming browser request is over SSL).
- You can set the "requireSSL" attribute on the <forms> configuration section within web.config files to have ASP.NET's forms-authentication system ensure that forms-authentication cookies are only set and used on SSL enabled pages and URLs. This avoids the risk of a hacker trying to intercept the authentication cookie on a non-SSL secured page, and then trying to use a "replay attack" from a different machine to impersonate a user.
For more information on IIS 7.0, please read my earlier IIS 7.0 overview blog post. Also make sure to check out the www.iis.net website. To read more of my "Tips and Tricks" blog posts, please visit my Tips and Tricks Summary Page. Hope this helps, Scott 
|
-
Recently some reports have been issued by security researchers describing ways hackers can use the JSON wire format used by most popular AJAX frameworks to try and exploit cross domain scripts within browsers. Specifically, these attacks use HTTP GET requests invoked via an HTML <script src=""> include element to circumvent the "same origin policy" enforced by browsers (which limits JavaScript objects like XmlHttpRequest to only calling URLs on the same domain that the page was loaded from), and then look for ways to exploit the JSON payload content. ASP.NET AJAX 1.0 includes a number of default settings and built-in features that prevent it from being susceptible to these types of JSON hijacking attacks. Below are some details of how these attacks are mitigated: ASP.NET AJAX Web Methods do not enable HTTP GET requests by default Script files loaded via an HTML <script src=""> element within a browser can only be retrieved via HTTP GET verb requests. By default ASP.NET AJAX's web services layer does not allow web methods to be invoked via the HTTP GET verb. For example, assume a developer writes a web service method like below: [WebMethod] public StockQuote[] GetQuotes(string symbol) {
}
ASP.NET will only allow the above GetQuotes method to be called via the HTTP POST verb, and will reject all attempts to invoke the method via an HTTP GET verb. To make an ASP.NET AJAX web-method callable via HTTP GET-access, a developer must explicitly attribute each method using ASP.NET's ScriptMethod attribute (and set the UseHttpGet property to true): [WebMethod] [ScriptMethod(UseHttpGet=true)]
public StockQuote[] GetQuotes(string symbol) {
}
Although this type of modification is easy to make, it requires a developer to intentionally GET enable a web service. ASP.NET AJAX web services can never be non-deliberately GET enabled, and the ASP.NET AJAX documentation explicitly recommends against GET enabling web-service end points for a number of reasons (risk of url tampering being one of them). Note: the ASP.NET AJAX "UpdatePanel" control, as well as the other server controls that ship with ASP.NET AJAX 1.0, do not use HTTP GET and instead use HTTP POSTs when doing asynchronous postbacks. ASP.NET AJAX Content-Type Header Validation There is built-in layer of protection that ASP.NET enforces for both GET and POST based ASP.NET AJAX web methods, which is that regardless of the HTTP verb being used, ASP.NET always requires that the HTTP Content-Type header is set to the value application/json. It this content type header is not sent, ASP.NET AJAX will reject the request on the server. Using the stock quote method shown earlier, an HTTP trace of an ASP.NET AJAX GET invocation must look like the following: GET /StockService/Stock.asmx/GetQuotes?symbol=%22msft%22 HTTP/1.1 Accept: */* Accept-Language: en-us,fr;q=0.5 Referer: http://xxxxxx/StockService/test.aspx Content-Type: application/json; charset=utf-8 UA-CPU: x86 Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2) Host: xxxxxx Proxy-Connection: Keep-Alive
Note that even though the above is a GET request, the client-side ASP.NET AJAX JSON stack still inserts a Content-Type HTTP header that tells the server to consider this an AJAX web service request. The server-side web services stack for ASP.NET AJAX 1.0 always checks for this specific content type, and if it is not found it will reject the request. If a malicious developer attempted a cross site request forgery attack using HTTP GETs against this web service, they might include a script tag in their page like the following: <script type="text/javascript" src="http://contoso.com/StockService/Stock.asmx/GetQuotes?symbol=msft" /> However, browsers will not set the Content-Type to application/json when parsing a <script src=""> element and making the request. As a result when ASP.NET receives a request made from a <script /> include, it will not recognize it as a request to an ASP.NET AJAX web service, and it will result in an error from ASP.NET stating that it does not recognize the requested URL. This will prevent JSON Hijacking attempts (even if you have the GET verb enabled for a web method). Summary ASP.NET AJAX 1.0 by default only allows the HTTP POST verb to be used when invoking web methods using JSON, which means you can't inadvertently allow browsers to invoke methods via HTTP GET. ASP.NET AJAX 1.0 requires a Content-Type header to be set to "application/json" for both GET and POST invocations to AJAX web services. JSON requests that do not contain this header will be rejected by an ASP.NET server. This means you cannot invoke an ASP.NET AJAX web method via a <script src=""> include because browsers do not allow append custom content-type headers when requesting a JavaScript file like this. Hope this helps, Scott 
|
-
One of the questions I am often asked is "How can I integrate ASP.NET security with Classic ASP other non-ASP.NET URLs?". Specifically, people want to know if they can integrate ASP.NET's Forms Authentication, Role Based Security, and URL Authorization features with Classic ASP, PHP, JSP, .HTM, .JPG and other non-ASP.NET URLs. The good news is that this is pretty easy with ASP.NET 2.0 and IIS 6.0 today, and will get even easier in the IIS 7.0 timeframe. The below blog post demonstrates how to integrate ASP.NET 2.0's Forms Authentication and Login/Membership features with classic ASP and static .HTML files. For a much more detailed walkthrough of how to achieve this (as well as how to integrate features like ASP.NET role based authorization with classic ASP applications), please read Chapter 6 of Stefan Schackow's excellent ASP.NET 2.0 Security, Membership, and Role Management book. Some Background on IIS 6.0 Wildcard Mappings IIS 6.0 with Windows Server 2003 added support for an ISAPI feature called "wildcard mappings". Wildcard mappings provide a way to configure IIS to cause all requests coming into the server to first be routed to one or more ISAPI extensions for processing. What is cool about wildcard mappings in IIS 6.0 is that after the ISAPI extension that is processing the wildcard extension is finished, it can then cause IIS to pass control of the request to the extension or internal handler within IIS that normally would process the request. ASP.NET 2.0 includes built-in support to take advantage of this wildcard mapping feature. This enables you to run ASP.NET code (or your own custom code) before and after the existing ISAPI extension that processes a non-ASP.NET URL (for example: a .asp, .php or .htm request) executes. We can use this feature to enable a bunch of cool integration features - including using ASP.NET authentication and authorization features to secure all URLs on a web-server. How to Configure an IIS 6.0 Wildcard Mapping For this sample I've created a new IIS application within the IIS 6.0 admin tool called "wildcardtest". It points to a directory that will contain a few files: "default.aspx", "login.aspx", "test.asp" and "test.htm" (these last two files being resources not usually handled by ASP.NET): By default when a URL request for a .aspx page comes to the application, the ASP.NET ISAPI will process the request. By default when a URL request for test.asp comes to the application, the classic ASP ISAPI will process the request - and no ASP.NET code will run. When a URL request for test.htm comes to the application, IIS6 will process the request internally - and again no ASP.NET code will ever run. We'll change this by enabling wildcard mappings for this application, and configure ASP.NET to run code before and after all requests to the server. To-do this, right-click on the application within the IIS Admin Tool and select the "properties" context menu item on it. This will bring up the application properties dialog: You can then click the "configuration" button to pull up the URL mapping rules for the application: Note how the top of this dialog lists the default ISAPI extension mappings (each URL extension is mapped to an ISAPI responsible for processing it). The bottom half of the dialog lists the "wildcard application map" rules. We can add a application wildcard mapping to the ASP.NET ISAPI by clicking the "insert" button, and pointing at the ASP.NET 2.0 ISAPI extension on disk: Very, Very Important: Make sure that you uncheck the "Verify this file exists" checkbox. If you don't do this ASP.NET URL resources like WebResource.axd and other URL's handled by ASP.NET that aren't backed by a physical file won't work anymore. This will lead to errors within your pages. Now close out all of the dialogs by clicking "ok" to accept the changes. You've now configured ASP.NET to be able to run code before and after each URL request into the application. Enabling Forms Authentication and Url Authorization for non-ASP.NET resources Once we've completed the above steps to register ASP.NET 2.0 as a wild-card mapping for all URLs into our IIS application, we can then use the standard ASP.NET authentication and authorization techniques to identify users in our application and grant/deny them access to it. For example, we could add a web.config file to our application that enables ASP.NET's forms authentication feature for this application, and sets up two URL Authorization rules that deny "anonymous" users access to both the test.asp and test.htm URLs: <?xml version="1.0"?>
<configuration>
<system.web> <authentication mode="Forms" /> </system.web>
<location path="test.asp">
<system.web> <authorization> <deny users="?"/> <allow users="*"/> </authorization> </system.web> </location>
<location path="test.htm">
<system.web> <authorization> <deny users="?"/> <allow users="*"/> </authorization> </system.web> </location>
</configuration> Now, when I attempt to access either "test.asp" or "test.htm", the ASP.NET authentication and authorization system will execute first to check whether I'm logged into the application with forms-authentication, and if not redirect me to the login.aspx page within my application for me to login: Note how the "ReturnUrl" used by ASP.NET's forms authentication system above has automatically set the "test.asp" url to redirect back to once I'm logged in (this works just like it would for a .aspx page). Once I successfully enter a username/password, I'll then have access to the test.asp page: Since the above page is a classic ASP file, I obviously don't have a "User.Identity.Name" property that I can use to identify the logged in user like I would in an ASP.NET page. However, I can retrieve the "AUTH_USER" ServerVariable value within classic ASP to obtain the username (ASP.NET automatically populates this before delegating the processing back to the classic ASP ISAPI). The code to use this from within classic ASP would look like below: <html> <body> <h1>Classic ASP Page</h1>
<h3> You are logged in as: <u> <%=Request.ServerVariables("AUTH_USER") %> </u> </h3> </body> </html> Click here to download a complete sample application that implements the above solution. By default it will use a SQL Express database to store ASP.NET 2.0's Membership and Role Management data. Alternatively, you can create and register a SQL 2000 or SQL 2005 database to store the membership and role management values. This older ASP.NET Security tutorial I did shows how to-do this. How to Learn More about ASP.NET Security I highly recommend buying a copy of Stefan Schackow's excellent ASP.NET 2.0 Security, Membership, and Role Management book. Stefan is a key member of the ASP.NET team, and owned and designed the security features in the ASP.NET 2.0 release. As such, he really, really, really knows what he is writing about. Chapter 6 of his book is titled "Integrating ASP.NET Security with Classic ASP" and contains much more detail about the solution I demonstrated above (as well as how to use role security with it, and pass data back and forth between ASP.NET and classic ASP). Click here to learn more about the book and/or buy it online. Other Online ASP.NET Security Resources I've published a number of ASP.NET Tips, Tricks, Recipes and Tutorials in the past that cover ASP.NET 2.0 security. Below is a short-list of them that you might want to review: For more free ASP.NET Tips, Tricks, and Tutorials I've written, please check out my ASP.NET Tips, Tricks and Tutorials listing. Hope this helps, Scott 
|
-
Recently I've helped a few people out who were having an issue with how they had added new Membership, Role, and Profile providers within their web.config file. If you are ever going to add a provider declaration within your web.config file, please read-on to learn how to avoid a common gotcha. Symptom: You want to configure ASP.NET 2.0 to store your Membership/Role Management/Profile data within a remote SQL database. To accomplish this you first use the aspnet_regsql.exe utility to provision the appropriate schema within the database. Rather than override the "LocalSqlServer" connection string within your web.config file, you decide to register a new provider within your web.config file like below (note: the following registration has a bug - so don't copy/paste it): <membership>
<providers> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="MyDatabase" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" /> </providers>
</membership> When registering the above provider you are careful to explictly set the applicationName property, and so avoid another really common gotcha. When you run your application on a machine without SQL Express, though, you see some weird behavior. You might get a SQL error like so: An error has occurred while establishing a connection to the server. When connecting to SQL Server 2005, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified) You might also find that the web administration tool has problems connecting with your database and/or the roles/users you create within it aren't correctly saved in the database you configured above. Cause of the Problem: The root cause of the above problem rests in how the new provider was registered within the web.config file. The <providers> section within the web.config file is implemented as a collection, and so it is possible to register multiple providers at the same time (this is useful when you want to have some users authenticated using one Membership store, and others authenticated using a separate Membership store). By default ASP.NET 2.0 registers a set of default SQL Express providers within the root web.config file on your machine that create a SQL Express database within your /app_data directory to store/manage membership/role/profile data when you first access it. Because this is registered at the machine-wide level, all provider collections by default inherit this registration. Unless you explictly <clear/> or override the inherited value, your application will have this default membership/role/profile provider registered. Because the above web.config file simply added a new provider -- and didn't clear or replace the default provider registration -- the above application now has two Membership providers configured. When you do a Membership.CreateUser() call in your code, ASP.NET will attempt to create the user in both membership databases. If you don't have SQL Express installed on your system, the create-user attempt will fail for this database - which leads to the errors and/or weird behavior above. How to Fix It: Unless you wish to register multiple membership, role or profile databases (which is rare), you should always add an explicit <clear/> directive before your <add/> statements within your web.config file: <membership>
<providers> <clear/> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="MyDatabase" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/" /> </providers>
</membership> This will ensure that your application doesn't inherit any default provider registrations. Note that you must do this for each provider declaration that you register. So if you are adding providers for <roles> and <profile>, make sure you add the <clear/> directive in their providers section as well. Hope this helps, Scott P.S. Click here to review other past ASP.NET Tips/Tricks, Gotchas, and Recipes. 
|
-
One of the most popular features in ASP.NET 2.0 was the introduction of the "provider model" for the built-in ASP.NET Application Service APIs. The provider model ensures that the storage/implementation for services like Membership, Roles, Profiles, Health Monitoring, Session State, and Site Navigation is extensible, and it allows a developer to easily plug-in alternative implementations if they want. Earlier this year I blogged about how we shipped the source code for the built-in ASP.NET 2.0 Providers on MSDN, and published 130+ pages of really good documentation that walksthrough their implementation and behavior. If you are looking to adapt the existing providers (which are pretty feature rich and in-depth), then you'll find these whitepapers and source a good resource to check-out. One downside with starting off with the code within the built-in feature-rich/optimized providers, though, is that the source code might be bigger and more complicated then you might like, and the database schema might not map to the database structure you want. Michal Valasek sent me mail earlier today pointing me at a really cool CodePlex project he has started that provides a simplified set of SQL Provider implementations for the ASP.NET Membership, Roles and Profile APIs. These use a nice straight-forward database schema and can be easily adapted for your application and/or integrated into existing tables you might already have. You can download the source code for them here. I definitely recommend checking them out and bookmarking the project - they provide a really great way to get started with custom providers. For learning more about the ASP.NET Provider Model, I also recommend checking out a great Code-Camp presentation that Paul Wilson did complete with a nice provider implementation built with his ORMapper that you can download here. You can also find other non-SQL provider implementations (including ones for MySql, SqlLite, Access and more) via my ASP.NET Security Resources Page. Hope this helps, Scott

|
-
Earlier this summer I posted two tutorials on using Windows Authentication with ASP.NET: Enabling Window Authentication within an Intranet ASP.NET Web Application and Implementing Role Based Security within ASP.NET using Windows Authentication and SQL Server. I also linked to Scott Mitchell's great ASP.NET 2.0 Security, Membership and Roles Tutorials that cover how to use Forms Authentication and the new Membership/Roles APIs in ASP.NET for Internet based web applications. These tutorials covers how to implement authentication on your site, which is the process of identifying who an incoming user is. They also demonstrate how to implement role based management on your site, which allows you to logically group individual users into higher-level roles or groups (for example: "admins", "friends", "subscribers", etc). The tutorials also demonstrate how to implement authorization rules to grant or deny users/roles access to visit individual pages or URLs within a site (the roles tutorial above also demonstrates how to show/hide menu nodes based on the permissions of the incoming user). Adding Security Authorization Rules to Business and Data Layers When you authenticate a user within an ASP.NET application, the authenticated user's identity will be automatically flowed throughout that user's request on the server. What this means is that you don't need to manually pass a user's identity around from method to method or class to class. This makes it much easier to implement security authorization rules throughout your application. One little known feature in .NET is the ability to have the CLR automatically use this identity information to authorize a user's capabilities before instantiating a class, or accessing a method/property on it. This makes it easy to add clean security authorization rules to your business and data layers without having to write much code. All you need to do to implement this is to use the PrincipalPermissionAttribute within the "System.Security.Permissions" namespace and decorate it on the appropriate class or member on it. For example: using System; using System.Security.Permissions;
[PrincipalPermission(SecurityAction.Demand, Authenticated = true)] public class EmployeeManager { [PrincipalPermission(SecurityAction.Demand, Role = "Manager")] public Employee LookupEmployee(int employeeID) { // todo }
[PrincipalPermission(SecurityAction.Demand, Role = "HR")] public void AddEmployee(Employee e) { // todo } } In the above example, I have added a PrincipalPermission attribute to the "EmployeeManager" class. By adding it I am requiring that a user must be authenticated (logged-in) before this class can be instantiated during a web request (the Authenticated=true demand enforces this). I have also then added two additional security demands on the "LookupEmployee" and "AddEmployee" methods. With the LookupEmployee method I am requiring that the authenticated user for the request is within the "Manager" role in order for the method to be invoked. With the AddEmployee method I am requiring that the authenticated user for the request is within the "HR" role in order for this method to be invoked and have a new Employee added to the system. And now if I accidentally introduce a security hole within my UI tier and have some code-path that allows a non-Manager/HR employee to cause these methods to be invoked, my business tier will automatically prevent this from happening and raise a security exception. The PrincipalPermissionAttribute isn't tied to any specific authentication mode. It will work with Forms Authentication, Windows Authentication, Passport Authentication, or any custom authentication mode you want to invent. It will also work with any Role implementation I might use (so if you build or plug-in your own Role Provider in ASP.NET it will just work). The PrincipalPermissionAttribute type is implemented in the standard CLR mscorlib assembly that all .NET projects compile against. So it isn't ASP.NET specific, and can be used within any application type (including Windows and Console applications). In addition to making it more generically useful, this makes it easier to unit-test business/data libraries built with it. Using PrincipalPermissionAttributes within Pages and Controls The PrincipalPermissionAttribute can be used on any class within an application. So in addition to using it within your business and data layers, you can also use it within ASP.NET pages or user-controls you author in your site as well. For example, to enforce that your "MyPage" page can only be used by those within the "Manager" role, you could add a PrincipalPermission attribute to the code-behind of it (below done in VB): Imports System.Security.Permissions
<PrincipalPermission(SecurityAction.Demand, Authenticated:=True, Role:="Manager")> _ Partial Class MyPage Inherits System.Web.UI.Page
End Class More on ASP.NET Security To learn more about ASP.NET security I'd recommend checking out my ASP.NET Security Resources list as well as my ASP.NET Tips and Tricks Page. There are also now two dedicated books on ASP.NET 2.0 Security out there: Stefan Schackow's Professional ASP.NET 2.0 Security, Membership and Role Management book and the new Developing More Secure Microsoft ASP.NET 2.0 Applications book by Dominick Baier (who runs the great http://www.leastprivilege.com/ blog) that just got published this week: Hope this helps, Scott

|
-
SQL Injection attacks are really nasty security vulnerabilities, and something all web developers (regardless of platform, technology or data layer) need to make sure they understand and protect themselves against. Unfortunately developers too often neglect putting focused time on this - and leave their applications (and worse their customers) extremely vulnerable. Michael Sutton recently published a very sobering post about just how widespread this issue is on the public web. He built a C# client application that uses the Google Search API to look for sites vulnerable to SQL Injection Attacks. The steps to achieve this were simple: - Look for sites that have querystring values (example: search for URLs with "id=" in the URL)
- Send a request to the sites identified as dynamic with an altered id= statement that adds an extra quote to attempt to cancel the SQL statement (example: id=6')
- Parse the response sent back to look for words like "SQL" and "query" - which typically indicate that the app is often sending back detailed error messages (also bad)
- Review whether the error message indicates that the parameter sent to SQL wasn't encoded correctly (in which case the site is open to SQL Injection Attacks)
Of a random sampling of 1000 sites he found via his Google search, he detected possible SQL Injection Attack vulnerability with 11.3% of them. That is really, really scary. It means hackers can remotely exploit the data in those applications, retrieve any unhashed/encrypted passwords or credit-card data, and potentially even log themselves in as administrators to the application. This is bad not only for the developer who built the application, but even worse for any consumer/user of the application who has provided data to the site thinking it will be secure. So what the heck is a SQL Injection Attack? There are a couple of scenarios that make SQL Injection attacks possible. The most common cause are cases where you are dynamically constructing SQL statements without using correctly encoded parameters. For example, consider this SQL query code that searches for Authors based on a social security number provided via a querystring: Dim SSN as String Dim SqlQuery as String
SSN = Request.QueryString("SSN") SqlQuery = "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + SSN + "'" If you have SQL code like the snippet above, then your entire database and application can be hacked remotely. How? Well in the normal scenario users will hit the site using a social security number which will be executed like so: ' URL to the page containing the above code http://mysite.com/listauthordetails.aspx?SSN=172-32-9999
' SQL Query executed against the database SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-9999' This does what the developer expected, and searches the database for author information filtered by the social security number. But because the parameter value hasn't been SQL encoded, a hacker could just as easily modify the querystring value to embed additional SQL statements after the value to execute. For example: ' URL to the page containing the above code http://mysite.com/listauthordetails.aspx?SSN=172-32-9999';DROP DATABASE pubs --
' SQL Query executed against the database SELECT au_lname, au_fname FROM authors WHERE au_id = '';DROP DATABASE pubs -- Notice how I was able to add the ';DROP DATABASE pubs -- clause to the SSN querystring value and use it to terminate the current SQL statement (via the ";" character), and then add my own malicious SQL statement to the string, and then comment out the rest of the statement (via the "--" characters). Because we are just manually concatenating the SQL statement in our code, we will end up passing this to the database - which will execute first the query against the authors table, and then delete our pubs database table. Bang - it is now gone. In case you think the idea of anonymous hackers deleting your database tables is bad, that is unfortunately actually one of the better scenarios when a SQL Injection Attack is involved. Rather than just destroy data, a hacker could instead use the above code vulnerability to perform a JOIN that retrieves all of the data within your database and displays it on the page (allowing them to retrieve username/passwords/credit-cards). They could also add UPDATE/INSERT statements to modify product prices, add new admin users, and really screw up your life (imagine auditing your inventory at the end of the month, only to discover that the actual number of products in your warehouse is different then what your accounting system reports...). How do you protect yourself? SQL Injection Attacks are something you need to worry about regardless of the web programming technology you are using (all web frameworks need to worry about it). A couple of very basic rules you must always follow: 1) Don't construct dynamic SQL Statements without using a type-safe parameter encoding mechanism. Most data APIs (including ADO + ADO.NET) have support for allowing you to specify the exact type of a parameter being provided (for example: string, integer, date) and can ensure that they are escaped/encoded for you to avoid hackers trying to exploit it. Always use these features. For example, with dynamic SQL using ADO.NET you could re-write the code above like below to make it safe: Dim SSN as String = Request.QueryString("SSN")
Dim cmd As new SqlCommand("SELECT au_lname, au_fname FROM authors WHERE au_id = @au_id") Dim param = new SqlParameter("au_id", SqlDbType.VarChar) param.Value = SSN cmd.Parameters.Add(param) This will prevent someone from trying to sneak in additional SQL expressions (since ADO.NET above knows to string encode the au_id value), and avoid other data problems (incorrectly type-casting values, etc). Note that the TableAdapter/DataSet designer built-into VS 2005 uses this mechanism automatically, as do the ASP.NET 2.0 data source controls. One common misperception is that if you are using SPROCs or a ORM you are completely safe from SQL Injection Attacks. This isn't true - you still need to make sure you are careful when you pass values to a SPROC, and/or when you escape or customize a query with an ORM that you do it in a safe way. 2) Always conduct a security review of your application before ever put it in production, and establish a formal security process to review all code anytime you make updates. This later point is super important. Too often I hear of teams that conduct a really detailed security review before going live, then have some "really minor" update they make to the site weeks/months later where they skip doing a security review ("it is just a tiny update - we'll code review it later"). Always do a security review. 3) Never store sensitive data in clear-text within a database. My personal opinion is that passwords should always be one-way hashed (I don't even like to store them encrypted). The ASP.NET 2.0 Membership API does this for you automatically by default (and also implements secure SALT randomization behavior). If you decide to build your own membership database store, I'd recommend checking out the source code for our own Membership provider implementation that we published here. Also make sure to encrypt credit-card and other private data in your database. This way even if your database was compromised, at least your customer private data can't be exploited. 4) Ensure you write automation unit tests that specifically verify your data access layer and application against SQL Injection attacks. This is really important to help catch the "it is just a tiny update so I'll be safe" scenario, and provide an additional safety layer to avoid accidentally introducing a bad security bug into your application. 5) Lock down your database to only grant the web application accessing it the minimal set of permissions that it needs to function. If the web application doesn't need access to certain tables, then make sure it doesn't have permissions to them. If it is only read-only generating reports from your account payables table then make sure you disable insert/update/delete access. How to Learn More The Microsoft Prescriptive Architecture Guidance (PAG) team has posted a number of really good security guideline documents that you should set aside some time to read: And these additional PAG How-To articles are useful for learning more about how to protect yourself against injection attacks: You can also find useful ASP.NET security information from this security blog post of mine, and from my ASP.NET Tips/Tricks page here. Hope this helps, Scott

|
-
I blogged about the cool new .NET and ASP.NET Guidance Explorer that the Microsoft Patterns and Practices Team has been working on back in June. Since then the team has been hard at work and recently moved the project to be hosted on CodePlex. You can visit and join the project here. All source code for the Guidance Explorer UI (which is of course built using .NET and Windows Forms) can also now be downloaded for free from the CodePlex project site. The Guidance Explorer provides a database of recommendations and best practices for ASP.NET, ADO.NET, and the .NET Framework that you can easily filter and search. Particularly useful are some of the checklists you can easily follow for performance and security best practices prior to deployment. You can create custom views of the library of just those recommendations you care about. What is cool is that you can now also easily publish these custom views to your friends or other members of your team: You will also be able to subscribe to feeds of guidance either on a local share or on the web: Best of all, you can also now create your own recommendations and add them into the library (and share them with others). Download the project (and optionally the full source code) from its CodePlex Project and start using it today! Hope this helps, Scott

|
-
Problem: You are developing/maintaining an ASP.NET web-site, and would like the ability to conditionally show/hide runtime error messages depending on who the user visiting the site is. For a normal user visiting the site you want to be able to display a friendly error message like this when a runtime error occurs:
But when someone within the “developers” security role of your application remotely accesses the site you want to instead show a more detailed exception stack trace error message about the problem without having to change any configuration data:
The below post describes how to use ASP.NET’s role-based security architecture in conjunction with the Global.asax Application_Error event handler to enable this. You can also download a sample I’ve built that shows how to implement this here. Some Background Discussion on Error Handling and ASP.NET Custom Error Pages: ASP.NET and .NET support a rich error-handling architecture that provides a flexible way to catch/handle errors at multiple levels within an application. Specifically, you can catch and handle a runtime exception with a class, within a page, or on the global application level using the Application_Error event handler within the Global.asax class. If a runtime exception isn’t handled/cancelled by one of these mechanisms, then ASP.NET’s Custom Error Page feature will kick-in, and an error page will be sent back to the browser accessing the application. ASP.NET’s Custom Error Page feature can be used to configure a “friendly error page” to be displayed to end-users in place of the standard “server error occurred” message sent back by ASP.NET. For example, the below web.config file section will cause remote users visiting the site to be redirected to a “friendlyErrorPage.htm” file anytime a runtime error occurs (note: HTTP 500 status code responses indicate runtime errors on the server): <customErrors mode="RemoteOnly"> <error statusCode="500" redirect="friendlyErrorPage.htm"/> </customErrors> To learn more about how the ASP.NET Custom Errors feature works, and how to configure it, please review this article. Important: I would recommend never setting the <customErrors> mode attribute to “Off”. Doing this will cause detailed error messages to be sent back to all normal users visiting your site. This can lead to information disclosure issues that can compromise the security of your site. Instead, only change this setting to “On” or “RemoteOnly” (the default) and use the technique below for cases where you want to display detailed error messages to only some users. Solution: The above <customErrors> configuration section will cause a friendly error message to be sent to the browser anytime a runtime error message occurs. This is what we want to happen anytime a normal user access the site, and will allow us to display error pages like this:
To enable developers to instead see detailed error messages when they access the site, I can then add this code to the Application_Error event handler within the Global.asax class: Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs) If (Context IsNot Nothing) And (Context.User.IsInRole("Developer")) Then Dim err As Exception = Server.GetLastError() Response.Clear() Response.Write("<h1>" & err.InnerException.Message & "</h1>") Response.Write("<pre>" & err.ToString & "</pre>") Server.ClearError() End If End Sub The above code checks to see if the current user visiting the site is within a specific security role. If so, then it retrieves the exception raised during the current request via the Server.GetLastError() method. It then clears out any content already generated during the current request, and instead outputs the Exception details to the response. Lastly, it clears out the Exception from the request – which will prevent ASP.NET’s custom error architecture from kicking in. The result instead is a page that details the error like so:
To learn more about how you can easily create and manage a “Developer” role like the one I’m using above, please review my previous post: Implementing Role Based Security with ASP.NET using Windows Authentication and SQL Server. If you don’t want to store your role-mappings within a database, you can also store them within Active Directory or your own custom role-provider. This sample demonstrates using a Windows User’s membership within the local “Administrators” group on a machine to control whether or not the detailed error message is displayed. Note: you can perform a role-check on a local Windows group by writing: User.IsInRole(“BUILTIN\Administrators”) – where the “BUILTIN” prefix indicates that it is a local group as opposed to a domain level one). Hope this helps, Scott P.S. Please visit this page to read more articles within my “ASP.NET 2.0 Tips/Tricks/Recipes and Gotchas” series.

|
-
Several people have sent me email lately asking for a suggested short-list of my best/favorite past blog posts to read (I’ve done 200 posts over the last 12 months and apparently it takes too long to read them all <g>). I’ve put together a summary page of ASP.NET 2.0 Tips, Tricks, Recipes and Gotchas that you can check out here. It currently contains links to 37 posts that I’ve done in the past that I think are interesting and worth spending sometime to read. I’ve organized the list by area topic (UI, Data, Security, Visual Studio, etc). My goal is to post at least 1-2 new/original ASP.NET Tips/Tricks/Recipes to my blog each week going forward. I’ll also make sure to update the summary page above as I add new ones – so you might find it useful to bookmark if you want to quickly look them up. Hope this | |
|