PART II: Securing Common ASP.NET Tasks
Chapter 7: Adding usernames and passwords
Authentication and Authorization
Discovering Your Own Identity
Adding Authentication in ASP.NET
Using Forms Authentication
Configuration Forms Authentication
Using SQL as a Membership Store
Creating Users
Examining How Users Are Stored
Configuring the Membership Settings
Creating Users Programmatically
Supporting Password Changes and Resets
Windows Authentication
Configuring IIS for Windows Authentication
Impersonation with Windows Authentication
If you want to pass the user’s identity to another system then you need to wrap the call in the follow code:
using (((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate())
{
// Perform database or network access here
}
Authorization in ASP.NET
Examining <allow> and <deny>
Role-Based Authorization
Configuring Roles with Forms-Based Authentication
Using the Configuration Tools to Manage Roles
Managing Roles Programmatically
Managing Role Members Programmatically
Limiting Access to Files and Folders
Checking Users and Roles Programmatically
Securing Object References
A checklist for Authentication and Authorization
- Do not roll your own unless you have to.—There are some cases where you may wish to develop own authentication and authorization functions, but doing so is fraught with potential mistakes. If you have an existing user database, then consider implementing the membership and roles provider models. This will enable you to use the standard methods to control access.
- Encourage your uses to logout.—Persistent authentication can lead to CSRF attacks. For high-value systems encourage users to log out by providing a visible and consistent logout button and do not provide “Remember Me” functionality.
- Always start with a deny access role.—Being specific in who you allow to access resources is safer than specifying who does not have access.
- Be aware of the difference between ASP.NET and IIS authorization rules.—IIS authorization rules run against every resource. ASP.NET authorization rules will only protect resources mapped to a managed code handler.
- If you use programmatic authorization checks to hide or display control, ensure those authorization checks run during execution of the underlying code.—If you show or hode user elements such as buttons based on roles or usernames, check again in any method bound to those buttons such as an OnClick() event.
- If resources belong to a user check the current user before serving them.—If a resource such as a message is for a particular user then check the current user has access to that resource.
Chapter 8: Securely accessing databases
Writing Bad Code: Demonstrating SQL Injection
Fixing the Vulnerability
More Security for SQL Server
Connecting Without Passwords
SQL Permissions
Adding a User to a Database
CREATE USER Olle FOR LOGIN Olle;
Managing SQL Permissions
GRANT SELECT ON Example TO PUCK\Guest
DENY SELECT ON Example TO Olle
REVOKE SELECT ON Example TO Olle
GRANT EXECUTE ON GetLogins TO Olle
Groups and Roles
CREATE ROLE auditors AUTHORIZATION db_owner;
EXEC sp_addrolemember 'auditors', 'PhilHa'
GRANT EXECUTE ON ReadAuditLogin TO auditors
Least Privilege Accounts
Using Views
SQL Express User Instances
Drawbacks of the VS Built-in Web Server
Dynamic SQL Stored Procedures
Wrong dynamic SQL stored procedures:
CREATE PROCEDURE search_orders @custId nchar(5) = NULL,
@shipTo nvarchar(40) = NULL AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ' SELECT OrderID, OrderDate, CustomerID, ShipTo ' +
' FROM dbo.Orders WHERE 1 = 1 '
IF @custid IS NOT NULL
SELECT @sql = @sql + ' AND custid LIKE ''' + @customerID + ''''
IF @shipTo IS NOT NULL
SELECT @sql = @sql + ' AND ShipTo LIKE ''' + @shipTo + ''''
EXEC(@sql)
Correct one:
CREATE PROCEDURE search_orders @custId nchar(5) = NULL,
@shipTo nvarchar(40) = NULL AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ' SELECT OrderID, OrderDate, CustomerID, ShipName ' +
' FROM dbo.Orders WHERE 1 = 1 '
IF @custid IS NOT NULL
SELECT @sql = @sql + ' AND CustomerID LIKE @custId '
IF @shipTo IS NOT NULL
SELECT @sql = @sql + ' AND ShipName LIKE @shipTo '
EXEC sp_executesql @sql, N'@custid nchar(5), @shipTo nvarchar(40)',
@custid, @shipTo
Using SQL Encryption
Encrypting by Pass Phrase
SQL Symmetric Entryption
SQL Asymmetric Encryption
Calculting Hashes and HMACs in SQL
A Checklist for securely Accessing Databases
- Never dynamically build SQL queries.—Dynamic queries are a vector for SQL injection.
- Always use SQL parameters.—SQL parameters will automatically escape dangerous characters and help you void SQL injection.
- Control access to your data.—If you can use stored procedures and SQL permissions to limit access to the underlying database. If you cannot use stored procedures, use updatable view to limit access to the underlying database. Stored procedures are not a panacea because they can, in turn, contain dynamic SQL themselves.
Chapter 9: Using the file system
Accessing Existing Files Safely
Making Static Files Secure
use Server.MapPath:
using System;
using System.IO;
public partial class getfile : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
string filename =
Path.GetFileName(Request.QueryString["filename"]);
FileInfo file = new FileInfo(
Server.MapPath(
Path.Combine("documents", filename)));
Response.AddHeader("Content - Length",
file.Length.ToString());
Response.ContentType = "text/plain";
Response.WriteFile(file.FullName);
Response.End();
}
Checking That Your Application Can Access Files
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
public partial class getfile : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
string filename = Path.GetFileName(Request.QueryString["filename"]);
string fullPath = Server.MapPath(
Path.Combine(@"c:\inetpub\wroxStaticFiles\", ?lename));
FileIOPermission accessPermission =
new FileIOPermission(FileIOPermissionAccess.Read, fullPath);
try
{
accessPermission.Demand();
FileInfo file = new FileInfo(
Server.MapPath(
Path.Combine("documents", filename)));
Response.AddHeader("Content-Length", file.Length.ToString());
Response.WriteFile(file.FullName);
}
catch (SecurityException)
{
Response.Write("Access Denied");
}
Response.End();
}
}
Making a File Downloadable and Setting Its Name
You can use the Response headers to set the filename of the file being served by using the Content-Disposition header like so:
Response.AddHeader("Content-Disposition", "filename=" & filename);
Furthermore, you can use Content-Disposition to cause a download rather than rendering in the browser:
Response.AddHeader("Content-Disposition", "attachment; filename=" & filename);
Adding Further Checks to File Access
Adding Role Checks
Anti-Leeching Checks
if (Request.UrlReferrer.Host != "mysite.example" &&
Request.UrlReferrer.Host != "www.mysite.example")
{
Response.End();
}
else
{
// Continue
}
Remember that, like anything from the HTTPRequest, the referrer is untrusted input and could be be faked by an attacker. Futhermore, some privacy software strips the referrer from a request, and a direct access to a file via its full URI will also mean the referrer is blank. You should always serve the file if the referrer is blank.
Accessing Files on a Remote System
Creating Files Safely
Path.GetTempFileName() will generate a filename with a .TMP extension located in the system temporary directory, and create a zero length file. However, the number of temporary file is limited to 65535 files, so old files must be cleaned as quickly as possible.
User Path.GetRandomFileName() to generates a cryptographically strong random value that you can create a file or directory.
A quick and easy scheduler using the ASP.NET cache:
<%@ Application Language="C#" %>
<script runat="server">
private const string CleanUpTask = "appCleanFiles";
private static CacheItemRemovedCallback OnCacheRemove = null;
void Application_Start(object sender, EventArgs e)
{
// code that runs on application startup
AddTask(CleanUpTask, 60);
}
private void AddTask(string taskName, int frequency)
{
OnCacheRemove = this.CacheItemRemoved;
HttpRuntime.Cache.Insert(taskName, frequency, null,
DateTime.Now.AddSeconds(frequency), Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, OnCacheRemove);
}
public void CacheItemRemoved(string key, object value,
CacheItemRemovedReason reason)
{
switch (key)
{
case CleanUpTask:
// Perform our cleanup here
break;
}
// re-add our task so it recurs
AddTask(key, Convert.ToInt32(value));
}
</script>
Handling User Uploads
Using the File Upload Control
For safety, you should ignore any filename sent with the upload request. Instead, you should create a random filename and save the original filename against it. You can then use the original filename as part of a ContentDisposition header when you serve the file back to users.
Change the maximum request size:
<?xml version="1.0"?>
<configuration >
....
<system.web>
<httpRuntime
executionTimeout = "90"
maxRequestLength="4096"
/>
....
</system.web>
....
</configuration>
WARNING: Increasing the maxRequestLength and executionTimeout properties may expose your application to a DOS attack. Experiment with your server configuration to discover the maximum values your hardware will support.
A Checklist for Securely Accessing Files
- Truncate user specific file name and extract just the file name from any potential path.—Use the Path.GetFileName() to safely remove all directory information.
- Serve content from a directory outside of your Web application.—If you must serve content from within your application path, then use Server.MapPath() to resolve directories. Server.MapPath() will stop any directory resolution from escaping out of the root of your application.
- Use code access Secruity demands to ensure your application has the ability to access the file system.—Remember that .NET applications have their own permissions in addition to underlying file system permissions.
- Create your own file names.—Use Path.GetRandomFileName() to generate filenames and directory names when you create files. This will avoid the overwriting of existing files, or any traps with reserved names and directory transversal attacks.
- Limit the maximum request size and execution timeout for your application to prevent DOS attacks.—Never trust any input from file uploads. Generate your own filenames and use indirect object references to retrieve these files.
Chapter 10: Securing XML
Validating XML
Well-Formed XML
Valid XML
XML Parsers
Querying XML
Avoid XPath Injection
string xPath =
"string(//Account[UserName/text()=$username"+
" and " +
"Password/text()=$password]/UserName/text())";
// This could be performed in a constructor to avoid
// the CPU hit needed each time an expression is compiled.
XPathNavigator navigator = xmlDocument.CreateNavigator();
XPathExpression expression = DynamicContext.Compile(xPath);
DynamicContext ctx = new DynamicContext();
ctx.AddVariable("username", username);
ctx.AddVariable("password", password);
expression.SetContext(ctx);
string account = Convert.ToString(navigator.Evaluate(expression));
Securing XML Documents
Encrypting XML Documents
Using a Symmetric Encryption Key with XML
Using an Asymmetric Key Pair to Encrypt and Decrypt XML
Using an X509 Certificate to Encrypt and Decrypt XML
Signing XML Documents
A Checklist for XML
- XML should be validated before trusting and using it.—Remember that the well-formed status of an XML document is not a guarantee of its validity.
- Validate all XML against a strict schema.—Use local copies of XML schemas whenever possible. If you need to use external schemas, consider caching them with a caching resolver.
- Choose and appropriate encryption method for your situation.—Generally, if your application needs to encrypt and decrypt the same data, choose a symmetric algorithm. If your application talks to an external system, choose an asymmetric algorithm.
- Always use digital signatures if you need to ensure data has not changed.—Encryption is not enough when you cannot detect changes in data. Even unencrypted data may need a mechanism for detecting changes. Use digital signing to provide a security mechanism against unauthorized modification.