Does ASP:Textbox TextMode Securely Enforce Input Validation?
Filed under: Development, Security
When building .Net Webform applications, the ASP:Textbox has a TextMode property that you can set. For example, you could indicate that the text should be a number by setting the property below:
<asp:TextBox ID=”txtNumber” runat=”server” TextMode=”Number” />
As you can see in the above example, we are specifically setting the TextMode attribute to Number. You can see a list of all the available modes at: https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.textboxmode?view=netframework-4.8.1.
But what does this actually mean? Is it limiting my input to just a number or can this be bypassed?
It is important to understand how this input validation works because we don’t want to make assumptions from a security perspective on what this field may contain. So many attacks start with the input and our first line of defense is input validation. Limiting a field to just a Number or a Date object can mitigate a lot of attacks. However, we need to be positive that the validation is enforced.
Let’s take a look at what this attribute is doing.
From a display standpoint, the simple code we entered above in our .ASPX page turns into the following in the browser:
<input name=”txtNumber” type=”number” id=”txtNumber” />
We can see that the TextMode attribute is controlling the Type attribute in the actual response. By default, the ASP:Textbox would return a type of Text, but here it is set to Number.
The number type will change the textbox that the user uses so that it is limiting to basically just numbers. The image below shows the new field.
If you try to submit the value with values other than numbers, it will display a message that indicates only numbers are allowed.
** Note that the restrictions for character input in the textbox itself may differ depending on the browser. Edge will only allow numbers and the letter ‘e’ to be input at all. If you try to enter ‘test’ it will only show the ‘e’. Whereas, Firefox will allow the characters to be added to the textbox. They both will alert the error though when clicking the submit button that it can only be numbers.
This initial testing validates that there is some client-side validation going on. This is great for immediate user feedback, but not for security. We need to ensure that the validation is happening on the server as well. It is too easy to bypass client-side validation and we should never trust it. Always verify your validation at the server level.
To test the server-side validation, we will just intercept the request using a web proxy. I use Burp Suite from Portswigger. Once you have the proxy configured we can turn intercept on and wait for the form to be submitted. Remember, the client-side validation is enforcing numbers, so we need to just enter a regular number in the textbox to submit the form. We will change this to something else in the intercept window. Here we can see the number passed up in the paused request.
Next, we will modify the number to a regular string.
Once we click turn intercept off, the request will now get to the code-behind. Let’s see what the Server now sees as the value of the textbox.
We can see that the value is “someTextString”, indicating that there is no validation happening on the server side. This means that while the TextMode will cause a change to how the textbox works on the client, it doesn’t have any effect on how it works on the server.
How do we add server side validation?
There are a few ways to do this, depending on how your team works. one way would be to try and parse the txtNumber.Text value into an Int or some other numeric type. If successful, you know you have just a number, if it fails, the data is no good.
Another way would be to add a regular expression validator to the form. This could look like this:
Here we have the regular expression ‘^[0-9]*$’ configured to only allow the digits 0-9. The great thing about these validators is that they provide both client and server-side validation out of the box. There is just one caveat to that. We have to make sure to check the Page.IsValid property, otherwise the server-side check will not be enforced. This would look like this:
With this new check, if the text matches the regular expression, then everything will work as expected. If it does not match, then an error message is returned indicating that it is invalid text.
Wrap Up
Understanding the framework and how different features work is critical in providing good security. It is easy to assume that because we set the type to number that the server will also enforce that. Unfortunately, that is not always the case. Here we clearly see that while client-side validation is being enforced, there are no matching enforcements on the server. This leaves our application open to multiple vulnerabilities, such as SQL Injection, Cross-Site Scripting, etc., depending on how that data is used.
Always make sure that your validation is happening at the server.
Open Redirect – Bad Implementation
I was recently looking through some code and happen to stumble across some logic that is attempting to prohibit the application from redirecting to an external site. While this sounds like a pretty simple task, it is common to see it incorrectly implemented. Lets look at the check that is being performed.
string url = Request.QueryString["returnUrl"]; if (string.IsNullOrWhiteSpace(url) || !url.StartsWith("/")) { Response.Redirect("~/default.aspx"); } else { Response.Redirect(url); }
The first thing I noticed was the line that checks to see if the url starts with a “/” characters. This is a common mistake when developers try to stop open redirection. The assumption is that to redirect to an external site one would need the protocol. For example, http://www.developsec.com. By forcing the url to start with the “/” character it is impossible to get the “http:” in there. Unfortunately, it is also possible to use //www.developsec.com as the url and it will also be interpreted as an absolute url. In the example above, by passing in returnUrl=//www.developsec.com the code will see the starting “/” character and allow the redirect. The browser would interpret the “//” as absolute and navigate to www.developsec.com.
After putting a quick test case together, I quickly proved out the point and was successful in bypassing this logic to enable a redirect to external sites.
Checking for Absolute or Relative Paths
ASP.Net has build in procedures for determining if a path is relative or absolute. The following code shows one way of doing this.
string url = Request.QueryString["returnUrl"]; Uri result; bool isAbsolute = false; isAbsolute = Uri.TryCreate(returnUrl, UriKind.Absolute, out result); if (!isAbsolute) { Response.Redirect(url); } else { Response.Redirect("~/default.aspx"); }
In the above example, if the URL is absolute (starts with a protocol, http/https, or starts with “//”) it will just redirect to the default page. If the url is not absolute, but relative, it will redirect to the url passed in.
While doing some research I came across a recommendation to use the following:
if (Uri.IsWellFormedUriString(returnUrl,UriKind.Relative))
When using the above logic, it flagged //www.developsec.com as a relative path which would not be what we are looking for. The previous logic correctly identified this as an absolute url. There may be other methods of doing this and MVC provides some other functions as well that we will cover in a different post.
Conclusion
Make sure that you have a solid understanding of the problem and the different ways it works. It is easy to overlook some of these different techniques. There is a lot to learn, and we should be learning every day.
.Net EnableHeaderChecking
How often do you take untrusted input and insert it into response headers? This could be in a custom header or in the value of a cookie. Untrusted user data is always a concern when it comes to the security side of application development and response headers are no exception. This is referred to as Response Splitting or HTTP Header Injection.
Like Cross Site Scripting (XSS), HTTP Header Injection is an attack that results from untrusted data being used in a response. In this case, it is in a response header which means that the context is what we need to focus on. Remember that in XSS, context is very important as it defines the characters that are potentially dangerous. In the HTML context, characters like < and > are very dangerous. In Header Injection the greatest concern is over the carriage return (%0D or \r) and new line (%0A or \n) characters, or CRLF. Response headers are separated by CRLF, indicating that if you can insert a CRLF then you can start creating your own headers or even page content.
Manipulating the headers may allow you to redirect the user to a different page, perform cross-site scripting attacks, or even rewrite the page. While commonly overlooked, this is a very dangerous flaw.
ASP.Net has a built in defense mechanism that is enabled by default called EnableHeaderChecking. When EnableHeaderChecking is enabled, CRLF characters are converted to %0D%0A and the browser does not recognize it as a new line. Instead, it is just displayed as characters, not actually creating a line break. The following code snippet was created to show how the response headers look when adding CRLF into a header.
public partial class _Default : Page { protected void Page_Load(object sender, EventArgs e) { Response.AppendHeader("test", "tes%0D%0At\r\ntest2"); Response.Cookies.Add(new HttpCookie("Test-Cookie", "Another%0D%0ATest\r\nCookie")); } }
When the application runs, it will properly encode the CRLF as shown in the image below.
My next step was to disable EnableHeaderChecking in the web.config file.
<httpRuntime targetFramework="4.6" enableHeaderChecking="false"/>
My expectation was that I would get a response that allowed the CRLF and would show me a line break in the response headers. To my surprise, I got the error message below:
So why the error? After doing a little Googling I found an article about ASP.Net 2.0 Breaking Changes on IIS 7.0. The item of interest is “13. IIS always rejects new lines in response headers (even if ASP.NET enableHeaderChecking is set to false)”
I didn’t realize this change had been implemented, but apparently, if using IIS 7.0 or above, ASP.Net won’t allow newline characters in a response header. This is actually good news as there is very little reason to allow new lines in a response header when if that was required, just create a new header. This is a great mitigation and with the default configuration helps protect ASP.Net applications from Response Splitting and HTTP Header Injection attacks.
Understanding the framework that you use and the server it runs on is critical in fully understanding your security risks. These built in features can really help protect an application from security risks. Happy and Secure coding!
I did a podcast on this topic which you can find on the DevelopSec podcast.
Potentially Dangerous Request.Path Value was Detected…
Filed under: Development, Security
I have discussed request validation many times when we see the potentially dangerous input error message when viewing a web page. Another interesting protection in ASP.Net is the built-in, on by default, Request.Path validation that occurs. Have you ever seen the error below when using or testing your application?
The screen above occurred because I placed the (*) character in the URL. In ASP.Net, there is a default set of defined illegal characters in the URL. This list is defined by RequestPathInvalidCharacters and can be configured in the web.config file. By default, the following characters are blocked:
- <
- >
- *
- %
- &
- :
- \\
It is important to note that these characters are blocked from being included in the URL, this does not include the protocol specification or the query string. That should be obvious since the query string uses the & character to separate parameters.
There are not many cases where your URL needs to use any of these default characters, but if there is a need to allow a specific character, you can override the default list. The override is done in the web.config file. The below snippet shows setting the new values (removing the < and > characters:
<httpruntime requestPathInvalidCharacters="*,%,&,:,\\"/>
Be aware that due the the web.config file being an xml file, you need to escape the < and > characters and set them as < and > respectively.
Remember that modifying the default security settings can expose your application to a greater security risk. Make sure you understand the risk of making these modifications before you perform them. It should be a rare occurrence to require a change to this default list. Understanding the platform is critical to understanding what is and is not being protected by default.
Securing The .Net Cookies
Filed under: Development, Security
I remember years ago when we talked about cookie poisoning, the act of modifying cookies to get the application to act differently. An example was the classic cookie used to indicate a user’s role in the system. Often times it would contain 1 for Admin or 2 for Manager, etc. Change the cookie value and all of a sudden you were the new admin on the block. You really don’t hear the phrase cookie poisoning anymore, I guess it was too dark.
There are still security risks around the cookies that we use in our application. I want to highlight 2 key attributes that help protect the cookies for your .Net application: Secure and httpOnly.
Secure Flag
The secure flag tells the browser that the cookie should only be sent to the server if the connection is using the HTTPS protocol. Ultimately this is indicating that the cookie must be sent over an encrypted channel, rather than over HTTP which is plain text.
HttpOnly Flag
The httpOnly flag tells the browser that the cookie should only be accessed to be sent to the server with a request, not by client-side scripts like JavaScript. This attribute helps protect the cookie from being stolen through cross-site scripting flaws.
Setting The Attributes
There are multiple ways to set these attributes of a cookie. Things get a little confusing when talking about session cookies or the forms authentication cookie, but I will cover that as I go. The easiest way to set these flags for all developer created cookies is through the web.config file. The following snippet shows the httpCookies element in the web.config.
<system.web> <authentication mode="None" /> <compilation targetframework="4.6" debug="true" /> <httpruntime targetframework="4.6" /> <httpcookies httponlycookies="true" requiressl="true" /> </system.web>
As you can see, you can set httponlycookies to true to se the httpOnly flag on all of the cookies. In addition, the requiressl setting sets the secure flag on all of the cookies with a few exceptions.
Some Exceptions
I stated earlier there are a few exceptions to the cookie configuration. The first I will discuss is the session cookie. The session cookie in ASP.Net is defaulted/hard-coded to set the httpOnly attribute. This should override any value set in the httpCookies element in the web.config. The session cookie does not default to requireSSL and setting that value in the httpCookies element as shown above should work just find for it.
The forms authentication cookie is another exception to the rules. Like the session cookie, it is hard-coded to httpOnly. The Forms element of the web.config has a requireSSL attribute that will override what is found in the httpCookies element. Simply put, if you don’t set requiressl=’true’ in the Forms element then the cookie will not have the secure flag even if requiressl=’true’ in the httpCookies element.
This is actually a good thing, even though it might not seem so yet. Here is the next thing about that Forms requireSSL setting.. When you set it, it will require that the web server is using a secure connection. Seems like common sense, but imagine a web farm where the load balancers offload SSL. In this case, while your web app uses HTTPS from client to server, in reality, the HTTPS stops at the load balancer and is then HTTP to the web server. This will throw an exception in your application.
I am not sure why Microsoft decided to make the decision to actually check this value, since the secure flag is a direction for the browser not the server. If you are in this situation you can still set the secure flag, you just need to do it a little differently. One option is to use your load balancer to set the flag when it sends any responses. Not all devices may support this so check with your vendor. The other option is to programmatically set the flag right before the response is sent to the user. The basic process is to find the cookie and just sent the .Secure property to ‘True’.
Final Thoughts
While there are other security concerns around cookies, I see the secure and httpOnly flag commonly misconfigured. While it does not seem like much, these flags go a long way to helping protect your application. ASP.Net has done some tricky configuration of how this works depending on the cookie, so hopefully this helps sort some of it out. If you have questions, please don’t hesitate to contact me. I will be putting together something a little more formal to hopefully clear this up a bit more in the near future.
ASP.Net Insufficient Session Timeout
Filed under: Development, Security, Testing
A common security concern found in ASP.Net applications is Insufficient Session Timeout. In this article, the focus is not on the ASP.Net session that is not effectively terminated, but rather the forms authentication cookie that is still valid after logout.
How to Test
- User is currently logged into the application.
- User captures the ASPAuth cookie (name may be different in different applications).
- Cookie can be captured using a browser plugin or a proxy used for request interception.
- User saves the captured cookie for later use.
- User logs out of the application.
- User requests a page on the application, passing the previously captured authentication cookie.
- The page is processed and access is granted.
Typical Logout Options
- The application calls FormsAuthentication.Signout()
- The application sets the Cookie.Expires property to a previous DateTime.
Cookie Still Works!!
Following the user process above, the cookie still provides access to the application as if the logout never occurred. So what is the deal? The key is that unlike a true “session” which is maintained on the server, the forms authentication cookie is self contained. It does not have a server side component to stay in sync with. Among other things, the authentication cookie has your username or ID, possibly roles, and an expiration date. When the cookie is received by the server it will be decrypted (please tell me you are using protection = all) and the data extracted. If the cookie’s internal expiration date has not passed, the cookie is accepted and processed as a valid cookie.
So what did FormsAuthentation.Signout() do?
If you look under the hood of the .Net framework, it has been a few years but I doubt much has changed, you will see that FormsAuthentication.Signout() really just removes the cookie from the browser. There is no code to perform any server function, it merely asks the browser to remove it by clearing the value and back-dating the expires property. While this does work to remove the cookie from the browser, it doesn’t have any effect on a copy of the original cookie you may have captured. The only sure way to really make the cookie inactive (before the internal timeout occurs) would be to change your machine key in the web.config file. This is not a reasonable solution.
Possible Mitigations
You should be protecting your cookie by setting the httpOnly and Secure properties. HttpOnly tells the browser not to allow javascript to have access to the cookie value. This is an important step to protect the cookie from theft via cross-site scripting. The secure flag tells the browser to only send the authentication cookie over HTTPS, making it much more difficult for an attacker to intercept the cookie as it is sent to the server.
Set a short timeout (15 minutes) on the cookie to decrease the window an attacker has to obtain the cookie.
You could attempt to build a tracking system to manage the authentication cookie on the server to disable it before its time has expired. Maybe something for another post.
Understand how the application is used to determine how risky this issue may be. If the application is not used on shared/public systems and the cookie is protected as mentioned above, the attack surface is significantly decreased.
Final Thoughts
If you are facing this type of finding and it is a forms authentication cookie issue, not the Asp.Net session cookie, take the time to understand the risk. Make sure you understand the settings you have and the priority and sensitivity of the application to properly understand “your” risk level. Don’t rely on third party risk ratings to determine how serious the flaw is. In many situations, this may be a low priority, however in the right app, this could be a high priority.